2478 | function Wheels:finalizeWheel(wheel, parentWheel) |
2479 | local spec = self.spec_wheels |
2480 | if parentWheel == nil and wheel.repr ~= nil then |
2481 | wheel.startPositionX, wheel.startPositionY, wheel.startPositionZ = getTranslation(wheel.repr) |
2482 | wheel.driveNodeStartPosX, wheel.driveNodeStartPosY, wheel.driveNodeStartPosZ = getTranslation(wheel.driveNode) |
2483 | wheel.dirtAmount = 0 |
2484 | wheel.xDriveOffset = 0 |
2485 | wheel.lastXDrive = 0 |
2486 | wheel.lastColor = {0,0,0,0} |
2487 | wheel.lastTerrainAttribute = 0 |
2488 | wheel.contact = Wheels.WHEEL_NO_CONTACT |
2489 | wheel.hasSnowContact = false |
2490 | wheel.snowScale = 0 |
2491 | wheel.lastSnowScale = 0 |
2492 | wheel.steeringAngle = 0 |
2493 | wheel.lastSteeringAngle = 0 |
2494 | wheel.lastMovement = 0 |
2495 | wheel.hasGroundContact = false |
2496 | wheel.hasHandbrake = true |
2497 | wheel.lastContactObjectAllowsTireTracks = true |
2498 | wheel.densityBits = 0 |
2499 | wheel.densityType = 0 |
2500 | |
2501 | local vehicleNode = self.vehicleNodes[wheel.node] |
2502 | if vehicleNode ~= nil and vehicleNode.component ~= nil and vehicleNode.component.motorized == nil then |
2503 | vehicleNode.component.motorized = true |
2504 | end |
2505 | |
2506 | if wheel.useReprDirection then |
2507 | wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.repr, wheel.node, 0,-1,0) |
2508 | wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.repr, wheel.node, 1,0,0) |
2509 | elseif wheel.useDriveNodeDirection then |
2510 | wheel.directionX, wheel.directionY, wheel.directionZ = localDirectionToLocal(wheel.driveNodeDirectionNode, wheel.node, 0,-1,0) |
2511 | wheel.axleX, wheel.axleY, wheel.axleZ = localDirectionToLocal(wheel.driveNodeDirectionNode, wheel.node, 1,0,0) |
2512 | else |
2513 | wheel.directionX, wheel.directionY, wheel.directionZ = 0,-1,0 |
2514 | wheel.axleX, wheel.axleY, wheel.axleZ = 1,0,0 |
2515 | end |
2516 | wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = 0,0,0 |
2517 | if wheel.repr ~= wheel.driveNode then |
2518 | wheel.steeringCenterOffsetX, wheel.steeringCenterOffsetY, wheel.steeringCenterOffsetZ = localToLocal(wheel.driveNode, wheel.repr, 0, 0, 0) |
2519 | wheel.steeringCenterOffsetX = -wheel.steeringCenterOffsetX |
2520 | wheel.steeringCenterOffsetY = -wheel.steeringCenterOffsetY |
2521 | wheel.steeringCenterOffsetZ = -wheel.steeringCenterOffsetZ |
2522 | end |
2523 | |
2524 | wheel.syncContactState = false |
2525 | |
2526 | if wheel.hasTireTracks then |
2527 | wheel.tireTrackNodeIndex = self:addTireTrackNode(wheel, false, wheel.node, wheel.repr, wheel.tireTrackAtlasIndex, wheel.width, wheel.radius, wheel.xOffset, wheel.tireIsInverted) |
2528 | wheel.syncContactState = true |
2529 | end |
2530 | |
2531 | wheel.maxLatStiffness = wheel.maxLatStiffness*wheel.restLoad |
2532 | wheel.maxLatStiffnessLoad = wheel.maxLatStiffnessLoad*wheel.restLoad |
2533 | |
2534 | wheel.mass = wheel.mass + wheel.additionalMass |
2535 | |
2536 | wheel.lastTerrainValue = 0 |
2537 | wheel.sink = 0 |
2538 | wheel.sinkTarget = 0 |
2539 | wheel.radiusOriginal = wheel.radius |
2540 | wheel.sinkFrictionScaleFactor = 1 |
2541 | wheel.sinkLongStiffnessFactor = 1 |
2542 | wheel.sinkLatStiffnessFactor = 1 |
2543 | |
2544 | local positionY = wheel.positionY+wheel.deltaY |
2545 | wheel.netInfo = {} |
2546 | wheel.netInfo.xDrive = 0 |
2547 | wheel.netInfo.xDriveSpeed = 0 |
2548 | wheel.netInfo.x = wheel.positionX |
2549 | wheel.netInfo.y = positionY |
2550 | wheel.netInfo.z = wheel.positionZ |
2551 | wheel.netInfo.suspensionLength = wheel.suspTravel*0.5 |
2552 | |
2553 | -- The suspension elongates by 20% of the specified susp travel |
2554 | wheel.netInfo.sync = {yMin = -5, yRange = 10} |
2555 | wheel.netInfo.yMin = positionY-1.2*wheel.suspTravel |
2556 | |
2557 | local width = 0.5 * wheel.width |
2558 | local length = math.min(0.5, 0.5 * wheel.width) |
2559 | local x, _, z = localToLocal(wheel.driveNode, wheel.repr, 0, 0, 0) |
2560 | wheel.destructionStartNode = createTransformGroup("destructionStartNode") |
2561 | wheel.destructionWidthNode = createTransformGroup("destructionWidthNode") |
2562 | wheel.destructionHeightNode = createTransformGroup("destructionHeightNode") |
2563 | link(wheel.repr, wheel.destructionStartNode) |
2564 | link(wheel.repr, wheel.destructionWidthNode) |
2565 | link(wheel.repr, wheel.destructionHeightNode) |
2566 | setTranslation(wheel.destructionStartNode, x + width, 0, z - length) |
2567 | setTranslation(wheel.destructionWidthNode, x - width, 0, z - length) |
2568 | setTranslation(wheel.destructionHeightNode, x + width, 0, z + length) |
2569 | |
2570 | -- |
2571 | self:updateWheelBase(wheel) |
2572 | self:updateWheelTireFriction(wheel) |
2573 | |
2574 | wheel.networkInterpolators = {} |
2575 | wheel.networkInterpolators.xDrive = InterpolatorAngle.new(wheel.netInfo.xDrive) |
2576 | wheel.networkInterpolators.position = InterpolatorPosition.new(wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z) |
2577 | wheel.networkInterpolators.suspensionLength = InterpolatorValue.new(wheel.netInfo.suspensionLength) |
2578 | end |
2579 | |
2580 | if parentWheel ~= nil then |
2581 | wheel.linkNode = createTransformGroup("linkNode") |
2582 | link(parentWheel.driveNode, wheel.linkNode) |
2583 | end |
2584 | |
2585 | if wheel.tireFilename ~= nil then |
2586 | local filename = Utils.getFilename(wheel.tireFilename, self.baseDirectory) |
2587 | wheel.tireFilename = nil |
2588 | local args = {wheel=wheel, |
2589 | parentWheel=parentWheel, |
2590 | linkNode=wheel.linkNode, |
2591 | name="wheelTire", |
2592 | filename=filename, |
2593 | fileIdentifier="tireFilename", |
2594 | index=wheel.tireNodeStr, |
2595 | offset=0, |
2596 | widthAndDiam=nil, |
2597 | scale=nil |
2598 | } |
2599 | local sharedLoadRequestId = self:loadSubSharedI3DFile(filename, false, false, self.onWheelPartI3DLoaded, self, args) |
2600 | table.insert(spec.sharedLoadRequestIds, sharedLoadRequestId) |
2601 | end |
2602 | if wheel.outerRimFilename ~= nil then |
2603 | local filename = Utils.getFilename(wheel.outerRimFilename, self.baseDirectory) |
2604 | wheel.outerRimFilename = nil |
2605 | local args = {wheel=wheel, |
2606 | parentWheel=parentWheel, |
2607 | linkNode=wheel.linkNode, |
2608 | name="wheelOuterRim", |
2609 | filename=filename, |
2610 | fileIdentifier="outerRimFilename", |
2611 | index=wheel.outerRimNodeStr, |
2612 | offset=0, |
2613 | widthAndDiam=wheel.outerRimWidthAndDiam, |
2614 | scale=wheel.outerRimScale |
2615 | } |
2616 | local sharedLoadRequestId = self:loadSubSharedI3DFile(filename, false, false, self.onWheelPartI3DLoaded, self, args) |
2617 | table.insert(spec.sharedLoadRequestIds, sharedLoadRequestId) |
2618 | end |
2619 | if wheel.innerRimFilename ~= nil then |
2620 | local filename = Utils.getFilename(wheel.innerRimFilename, self.baseDirectory) |
2621 | wheel.innerRimFilename = nil |
2622 | local args = {wheel=wheel, |
2623 | parentWheel=parentWheel, |
2624 | linkNode=wheel.linkNode, |
2625 | name="wheelInnerRim", |
2626 | filename=filename, |
2627 | fileIdentifier="innerRimFilename", |
2628 | index=wheel.innerRimNodeStr, |
2629 | offset=wheel.innerRimOffset, |
2630 | widthAndDiam=wheel.innerRimWidthAndDiam, |
2631 | scale=wheel.innerRimScale |
2632 | } |
2633 | local sharedLoadRequestId = self:loadSubSharedI3DFile(filename, false, false, self.onWheelPartI3DLoaded, self, args) |
2634 | table.insert(spec.sharedLoadRequestIds, sharedLoadRequestId) |
2635 | end |
2636 | if wheel.additionalFilename ~= nil then |
2637 | local filename = Utils.getFilename(wheel.additionalFilename, self.baseDirectory) |
2638 | wheel.additionalFilename = nil |
2639 | local args = {wheel=wheel, |
2640 | parentWheel=parentWheel, |
2641 | linkNode=wheel.linkNode, |
2642 | name="wheelAdditional", |
2643 | filename=filename, |
2644 | fileIdentifier="additionalFilename", |
2645 | index=wheel.additionalNodeStr, |
2646 | offset=wheel.additionalOffset, |
2647 | widthAndDiam=wheel.additionalWidthAndDiam, |
2648 | scale=wheel.additionalScale |
2649 | } |
2650 | local sharedLoadRequestId = self:loadSubSharedI3DFile(filename, false, false, self.onWheelPartI3DLoaded, self, args) |
2651 | table.insert(spec.sharedLoadRequestIds, sharedLoadRequestId) |
2652 | end |
2653 | |
2654 | if wheel.additionalWheels ~= nil then |
2655 | local outmostWheelWidth = 0 |
2656 | local totalWheelShapeOffset = 0 |
2657 | local offsetDir = 1 |
2658 | for _, additionalWheel in pairs(wheel.additionalWheels) do |
2659 | self:finalizeWheel(additionalWheel, wheel) |
2660 | |
2661 | local baseWheelWidth = MathUtil.mToInch(wheel.width) |
2662 | local dualWheelWidth = MathUtil.mToInch(additionalWheel.width) |
2663 | local diameter = 0 |
2664 | local wheelOffset = MathUtil.mToInch(additionalWheel.offset) |
2665 | |
2666 | if wheel.outerRimWidthAndDiam ~= nil then |
2667 | baseWheelWidth = wheel.outerRimWidthAndDiam[1] |
2668 | diameter = wheel.outerRimWidthAndDiam[2] |
2669 | end |
2670 | if additionalWheel.outerRimWidthAndDiam ~= nil then |
2671 | dualWheelWidth = additionalWheel.outerRimWidthAndDiam[1] |
2672 | end |
2673 | |
2674 | if wheelOffset < 0 then |
2675 | offsetDir = additionalWheel.isLeft and -1 or 1 |
2676 | else |
2677 | offsetDir = additionalWheel.isLeft and 1 or -1 |
2678 | end |
2679 | |
2680 | local totalOffset = 0 |
2681 | totalOffset = totalOffset + (additionalWheel.isLeft and 1 or -1) * MathUtil.inchToM(0.5*baseWheelWidth + wheelOffset + 0.5*dualWheelWidth ) |
2682 | |
2683 | -- get wheel with furthest offset |
2684 | if math.abs(totalOffset) > math.abs(totalWheelShapeOffset) then |
2685 | totalWheelShapeOffset = math.abs(totalOffset) |
2686 | outmostWheelWidth = additionalWheel.width |
2687 | end |
2688 | |
2689 | if additionalWheel.connector ~= nil then |
2690 | local filename = Utils.getFilename(additionalWheel.connector.filename, self.baseDirectory) |
2691 | additionalWheel.connector.filename = nil |
2692 | |
2693 | local arguments = { |
2694 | wheel = wheel, |
2695 | connector = additionalWheel.connector, |
2696 | diameter = diameter, |
2697 | baseWheelWidth = baseWheelWidth, |
2698 | wheelDistance = wheelOffset, |
2699 | offsetDir = offsetDir, |
2700 | dualWheelWidth = dualWheelWidth, |
2701 | filename = filename |
2702 | } |
2703 | local sharedLoadRequestId = self:loadSubSharedI3DFile(filename, false, false, self.onAdditionalWheelConnectorI3DLoaded, self, arguments) |
2704 | table.insert(spec.sharedLoadRequestIds, sharedLoadRequestId) |
2705 | end |
2706 | |
2707 | local x,y,z = getTranslation(additionalWheel.linkNode) |
2708 | setTranslation(additionalWheel.linkNode, x+totalOffset,y,z) |
2709 | |
2710 | if additionalWheel.hasTireTracks then |
2711 | additionalWheel.tireTrackNodeIndex = self:addTireTrackNode(wheel, true, wheel.node, additionalWheel.linkNode, additionalWheel.tireTrackAtlasIndex, additionalWheel.width, wheel.radius, wheel.xOffset, wheel.tireIsInverted) |
2712 | end |
2713 | |
2714 | if additionalWheel.driveGroundParticleSystems ~= nil then |
2715 | for name, particleSystems in pairs(additionalWheel.driveGroundParticleSystems) do |
2716 | for i=1, #particleSystems do |
2717 | local ps = particleSystems[i] |
2718 | ps.offsets[1] = ps.offsets[1] + totalOffset |
2719 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)) |
2720 | setTranslation(ps.emitterShape, wx + ps.offsets[1], wy + ps.offsets[2], wz + ps.offsets[3]) |
2721 | table.insert(wheel.driveGroundParticleSystems[name], ps) |
2722 | end |
2723 | end |
2724 | end |
2725 | end |
2726 | |
2727 | wheel.widthOffset = wheel.widthOffset + offsetDir * (totalWheelShapeOffset/2) |
2728 | wheel.wheelShapeWidthTotalOffset = totalWheelShapeOffset |
2729 | wheel.wheelShapeWidthTmp = wheel.width/2 + outmostWheelWidth/2 |
2730 | end |
2731 | end |
2998 | function Wheels:getVehicleWorldDirection(superFunc) |
2999 | local avgDirX, avgDirY, avgDirZ, _ = 0, 0, 0 |
3000 | local centerZ = 0 |
3001 | local contactedWheels = 0 |
3002 | local spec = self.spec_wheels |
3003 | for i=1, #spec.wheels do |
3004 | local wheel = spec.wheels[i] |
3005 | if wheel.hasGroundContact then |
3006 | local _, _, z = localToLocal(wheel.node, self.components[1].node, wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z) |
3007 | centerZ = centerZ + z |
3008 | |
3009 | local dx, dy, dz = localDirectionToWorld(wheel.node, wheel.directionZ, wheel.directionX, -wheel.directionY) |
3010 | avgDirX, avgDirY, avgDirZ = avgDirX + dx, avgDirY + dy, avgDirZ + dz |
3011 | |
3012 | contactedWheels = contactedWheels + 1 |
3013 | end |
3014 | end |
3015 | |
3016 | if contactedWheels > 0 then |
3017 | avgDirX, _, avgDirZ = avgDirX / contactedWheels, avgDirY / contactedWheels, avgDirZ / contactedWheels |
3018 | end |
3019 | |
3020 | if contactedWheels > 2 then |
3021 | centerZ = centerZ / contactedWheels |
3022 | |
3023 | local frontCenterX, frontCenterY, frontCenterZ, frontWheelsCount = 0, 0, 0, 0 |
3024 | local backCenterX, backCenterY, backCenterZ, backWheelsCount = 0, 0, 0, 0 |
3025 | |
3026 | for i=1, #spec.wheels do |
3027 | local wheel = spec.wheels[i] |
3028 | if wheel.hasGroundContact then |
3029 | local x, y, z = localToLocal(wheel.node, self.components[1].node, wheel.netInfo.x + wheel.directionX * wheel.radius, wheel.netInfo.y + wheel.directionY * wheel.radius, wheel.netInfo.z + wheel.directionZ * wheel.radius) |
3030 | |
3031 | if z > centerZ + 0.25 then |
3032 | frontCenterX, frontCenterY, frontCenterZ, frontWheelsCount = frontCenterX + x, frontCenterY + y, frontCenterZ + z, frontWheelsCount + 1 |
3033 | elseif z < centerZ - 0.25 then |
3034 | backCenterX, backCenterY, backCenterZ, backWheelsCount = backCenterX + x, backCenterY + y, backCenterZ + z, backWheelsCount + 1 |
3035 | end |
3036 | end |
3037 | end |
3038 | |
3039 | if frontWheelsCount > 0 and backWheelsCount > 0 then |
3040 | frontCenterX, frontCenterY, frontCenterZ = frontCenterX / frontWheelsCount, frontCenterY / frontWheelsCount, frontCenterZ / frontWheelsCount |
3041 | backCenterX, backCenterY, backCenterZ = backCenterX / backWheelsCount, backCenterY / backWheelsCount, backCenterZ / backWheelsCount |
3042 | |
3043 | frontCenterX, frontCenterY, frontCenterZ = localToWorld(self.components[1].node, frontCenterX, frontCenterY, frontCenterZ) |
3044 | backCenterX, backCenterY, backCenterZ = localToWorld(self.components[1].node, backCenterX, backCenterY, backCenterZ) |
3045 | |
3046 | if VehicleDebug.state == VehicleDebug.DEBUG_TRANSMISSION then |
3047 | DebugUtil.drawDebugGizmoAtWorldPos(frontCenterX, frontCenterY, frontCenterZ, 1, 0, 0, 0, 1, 0, "frontWheels", false) |
3048 | DebugUtil.drawDebugGizmoAtWorldPos(backCenterX, backCenterY, backCenterZ, 1, 0, 0, 0, 1, 0, "backWheels", false) |
3049 | end |
3050 | |
3051 | local dx, dy, dz, _ = frontCenterX - backCenterX, frontCenterY - backCenterY, frontCenterZ - backCenterZ |
3052 | _, avgDirY, _ = MathUtil.vector3Normalize(dx, dy, dz) |
3053 | else |
3054 | return superFunc(self) |
3055 | end |
3056 | else |
3057 | return 0, 0, 0 |
3058 | end |
3059 | |
3060 | return MathUtil.vector3Normalize(avgDirX, avgDirY, avgDirZ) |
3061 | end |
187 | function Wheels.initSpecialization() |
188 | g_configurationManager:addConfigurationType("wheel", g_i18n:getText("configuration_wheelSetup"), "wheels", nil, Wheels.loadBrandName, Wheels.loadedBrandNames, ConfigurationUtil.SELECTOR_MULTIOPTION, g_i18n:getText("configuration_wheelBrand"), Wheels.getBrands, Wheels.getWheelsByBrand) |
189 | g_configurationManager:addConfigurationType("rimColor", g_i18n:getText("configuration_rimColor"), nil, nil, ConfigurationUtil.getConfigColorSingleItemLoad, ConfigurationUtil.getConfigColorPostLoad, ConfigurationUtil.SELECTOR_COLOR) |
190 | |
191 | g_storeManager:addSpecType("wheels", "shopListAttributeIconWheels", Wheels.loadSpecValueWheels, Wheels.getSpecValueWheels, "vehicle") |
192 | |
193 | g_storeManager:addVRamUsageFunction(Wheels.getVRamUsageFromXML) |
194 | |
195 | local schema = Vehicle.xmlSchema |
196 | schema:setXMLSpecializationType("Wheels") |
197 | |
198 | ConfigurationUtil.registerColorConfigurationXMLPaths(schema, "rimColor") |
199 | ObjectChangeUtil.registerObjectChangeXMLPaths(schema, "vehicle.wheels.wheelConfigurations.wheelConfiguration(?)") |
200 | schema:register(XMLValueType.STRING, "vehicle.wheels.wheelConfigurations.wheelConfiguration(?)#brand", "Wheel brand") |
201 | |
202 | local configKey = Wheels.WHEELS_XML_PATH |
203 | schema:register(XMLValueType.FLOAT, configKey .. "#autoRotateBackSpeed", "Auto rotate back speed", 1) |
204 | schema:register(XMLValueType.BOOL, configKey .. "#speedDependentRotateBack", "Speed dependent auto rotate back speed", true) |
205 | schema:register(XMLValueType.INT, configKey .. "#differentialIndex", "Differential index") |
206 | schema:register(XMLValueType.INT, configKey .. "#ackermannSteeringIndex", "Ackermann steering index") |
207 | schema:register(XMLValueType.STRING, configKey .. "#baseConfig", "Base for this configuration") |
208 | |
209 | schema:register(XMLValueType.BOOL, configKey .. "#hasSurfaceSounds", "Has surface sounds", true) |
210 | schema:register(XMLValueType.STRING, configKey .. "#surfaceSoundTireType", "Tire type that is used for surface sounds", "Tire type of first wheel") |
211 | schema:register(XMLValueType.NODE_INDEX, configKey .. "#surfaceSoundLinkNode", "Surface sound link node", "Root component") |
212 | |
213 | Wheels.registerWheelXMLPaths(schema, configKey .. ".wheel(?)") |
214 | |
215 | schema:register(XMLValueType.COLOR, "vehicle.wheels.rimColor", "Rim color") |
216 | schema:register(XMLValueType.BOOL, "vehicle.wheels.rimColor#useBaseColor", "Use base vehicle color", false) |
217 | schema:register(XMLValueType.INT, "vehicle.wheels.rimColor#material", "Material id") |
218 | |
219 | for i = 0, 7 do |
220 | schema:register(XMLValueType.COLOR, "vehicle.wheels.hubs.color" .. i, "Color") |
221 | schema:register(XMLValueType.INT, "vehicle.wheels.hubs.color" .. i .. "#material", "Material id") |
222 | schema:register(XMLValueType.BOOL, "vehicle.wheels.hubs.color" .. i .. "#useBaseColor", "Use base color", false) |
223 | schema:register(XMLValueType.BOOL, "vehicle.wheels.hubs.color" .. i .. "#useRimColor", "Use rim color", false) |
224 | end |
225 | |
226 | schema:register(XMLValueType.NODE_INDEX, "vehicle.wheels.hubs.hub(?)#linkNode", "Link node") |
227 | schema:register(XMLValueType.STRING, "vehicle.wheels.hubs.hub(?)#filename", "Filename") |
228 | schema:register(XMLValueType.BOOL, "vehicle.wheels.hubs.hub(?)#isLeft", "Is left side", false) |
229 | |
230 | for i = 0, 7 do |
231 | schema:register(XMLValueType.STRING, "vehicle.wheels.hubs.hub(?).color" .. i, "Color") |
232 | schema:register(XMLValueType.INT, "vehicle.wheels.hubs.hub(?).color" .. i .. "#material", "Material id") |
233 | end |
234 | |
235 | schema:register(XMLValueType.FLOAT, "vehicle.wheels.hubs.hub(?)#offset", "X axis offset") |
236 | schema:register(XMLValueType.VECTOR_SCALE, "vehicle.wheels.hubs.hub(?)#scale", "Hub scale") |
237 | |
238 | schema:addDelayedRegistrationFunc("AnimatedVehicle:part", function(cSchema, cKey) |
239 | cSchema:register(XMLValueType.INT, cKey .. "#wheelIndex", "Wheel index [1..n]") |
240 | cSchema:register(XMLValueType.ANGLE, cKey .. "#startSteeringAngle", "Start steering angle") |
241 | cSchema:register(XMLValueType.ANGLE, cKey .. "#endSteeringAngle", "End steering angle") |
242 | cSchema:register(XMLValueType.FLOAT, cKey .. "#startBrakeFactor", "Start brake force factor") |
243 | cSchema:register(XMLValueType.FLOAT, cKey .. "#endBrakeFactor", "End brake force factor") |
244 | end) |
245 | |
246 | local dynaWheelKey = "vehicle.wheels.dynamicallyLoadedWheels.dynamicallyLoadedWheel(?)" |
247 | schema:register(XMLValueType.NODE_INDEX, dynaWheelKey .. "#linkNode", "Link node") |
248 | schema:register(XMLValueType.STRING, dynaWheelKey .. "#filename", "Path to wheel xml file") |
249 | schema:register(XMLValueType.STRING, dynaWheelKey .. "#configId", "Wheel config id", "default") |
250 | schema:register(XMLValueType.BOOL, dynaWheelKey .. "#isLeft", "Is left", true) |
251 | schema:register(XMLValueType.BOOL, dynaWheelKey .. "#isInverted", "Tire profile inverted", false) |
252 | schema:register(XMLValueType.ANGLE, dynaWheelKey .. "#xRotOffset", "X rotation offset", 0) |
253 | schema:register(XMLValueType.COLOR, dynaWheelKey .. "#color", "Rim color") |
254 | schema:register(XMLValueType.COLOR, dynaWheelKey .. "#additionalColor", "Color of additional part") |
255 | |
256 | Wheels.registerAckermannSteeringXMLPaths(schema, "vehicle.wheels.ackermannSteeringConfigurations.ackermannSteering(?)") |
257 | |
258 | schema:setXMLSpecializationType() |
259 | |
260 | Wheels.xmlSchema = XMLSchema.new("wheel") |
261 | Wheels.xmlSchema:register(XMLValueType.STRING, "wheel.brand", "Wheel tire brand", "LIZARD") |
262 | Wheels.xmlSchema:register(XMLValueType.STRING, "wheel.name", "Wheel tire name", "Tire") |
263 | Wheels.registerWheelSharedDataXMLPaths(Wheels.xmlSchema, "wheel.default") |
264 | Wheels.registerWheelSharedDataXMLPaths(Wheels.xmlSchema, "wheel.configurations.configuration(?)") |
265 | Wheels.xmlSchema:register(XMLValueType.STRING, "wheel.configurations.configuration(?)#id", "Configuration Id") |
266 | |
267 | Wheels.xmlSchemaHub = XMLSchema.new("wheelHub") |
268 | local hubSchema = Wheels.xmlSchemaHub |
269 | hubSchema:register(XMLValueType.STRING, "hub.filename", "I3D filename") |
270 | hubSchema:register(XMLValueType.STRING, "hub.nodes#left", "Index of left node in hub i3d file") |
271 | hubSchema:register(XMLValueType.STRING, "hub.nodes#right", "Index of right node in hub i3d file") |
272 | hubSchema:register(XMLValueType.COLOR, "hub.color0", "Color 0") |
273 | hubSchema:register(XMLValueType.COLOR, "hub.color1", "Color 1") |
274 | hubSchema:register(XMLValueType.COLOR, "hub.color2", "Color 2") |
275 | hubSchema:register(XMLValueType.COLOR, "hub.color3", "Color 3") |
276 | hubSchema:register(XMLValueType.COLOR, "hub.color4", "Color 4") |
277 | hubSchema:register(XMLValueType.COLOR, "hub.color5", "Color 5") |
278 | hubSchema:register(XMLValueType.COLOR, "hub.color6", "Color 6") |
279 | hubSchema:register(XMLValueType.COLOR, "hub.color7", "Color 7") |
280 | |
281 | Wheels.xmlSchemaConnector = XMLSchema.new("wheelConnector") |
282 | local connectorSchema = Wheels.xmlSchemaConnector |
283 | connectorSchema:register(XMLValueType.STRING, "connector.file#name", "I3D filename") |
284 | connectorSchema:register(XMLValueType.STRING, "connector.file#leftNode", "Index of left node in connector i3d file") |
285 | connectorSchema:register(XMLValueType.STRING, "connector.file#rightNode", "Index of right node in connector i3d file") |
286 | |
287 | local schemaSavegame = Vehicle.xmlSchemaSavegame |
288 | schemaSavegame:register(XMLValueType.INT, "vehicles.vehicle(?).wheels#lastWheelConfiguration", "Last selected wheel configuration", 1) |
289 | end |
2272 | function Wheels:loadAckermannSteeringFromXML(xmlFile, ackermannSteeringIndex) |
2273 | local spec = self.spec_wheels |
2274 | |
2275 | local key, _ = ConfigurationUtil.getXMLConfigurationKey(xmlFile, ackermannSteeringIndex, "vehicle.wheels.ackermannSteeringConfigurations.ackermannSteering", nil, "ackermann") |
2276 | |
2277 | spec.steeringCenterNode = nil |
2278 | |
2279 | if key ~= nil then |
2280 | local rotSpeed = xmlFile:getValue(key.."#rotSpeed") |
2281 | local rotMax = xmlFile:getValue(key.."#rotMax") |
2282 | |
2283 | local centerX |
2284 | local centerZ |
2285 | local rotCenterWheel1 = xmlFile:getValue(key.."#rotCenterWheel1") |
2286 | if rotCenterWheel1 ~= nil and spec.wheels[rotCenterWheel1] ~= nil then |
2287 | local wheel = spec.wheels[rotCenterWheel1] |
2288 | centerX, _, centerZ = localToLocal(wheel.node, self.components[1].node, wheel.positionX, wheel.positionY, wheel.positionZ) |
2289 | |
2290 | local rotCenterWheel2 = xmlFile:getValue(key.."#rotCenterWheel2") |
2291 | if rotCenterWheel2 ~= nil and spec.wheels[rotCenterWheel2] ~= nil then |
2292 | if rotCenterWheel2 == rotCenterWheel1 then |
2293 | Logging.xmlWarning(xmlFile, "The ackermann steering wheels are identical (both index %d). Are you sure this is correct? (%s)", rotCenterWheel1, key) |
2294 | end |
2295 | |
2296 | local wheel2 = spec.wheels[rotCenterWheel2] |
2297 | local x, _, z = localToLocal(wheel2.node, self.components[1].node, wheel2.positionX, wheel2.positionY, wheel2.positionZ) |
2298 | centerX, centerZ = 0.5*(centerX + x), 0.5*(centerZ + z) |
2299 | end |
2300 | else |
2301 | local centerNode, _ = xmlFile:getValue(key.."#rotCenterNode", nil, self.components, self.i3dMappings) |
2302 | if centerNode ~= nil then |
2303 | centerX, _, centerZ = localToLocal(centerNode, self.components[1].node, 0,0,0) |
2304 | spec.steeringCenterNode = centerNode |
2305 | else |
2306 | local p = xmlFile:getValue(key.."#rotCenter", nil, true) |
2307 | if p ~= nil then |
2308 | centerX = p[1] |
2309 | centerZ = p[2] |
2310 | end |
2311 | end |
2312 | end |
2313 | if spec.steeringCenterNode == nil then |
2314 | spec.steeringCenterNode = createTransformGroup("steeringCenterNode") |
2315 | link(self.components[1].node, spec.steeringCenterNode) |
2316 | if centerX ~= nil and centerZ ~= nil then |
2317 | setTranslation(spec.steeringCenterNode, centerX, 0, centerZ) |
2318 | end |
2319 | end |
2320 | |
2321 | if rotSpeed ~= nil and rotMax ~= nil and centerX ~= nil then |
2322 | rotSpeed = math.abs(math.rad(rotSpeed)) |
2323 | rotMax = math.abs(math.rad(rotMax)) |
2324 | |
2325 | -- find the wheel that should get the maximum steering (the one that results in the maximum turnign radius) |
2326 | local maxTurningRadius = 0 |
2327 | local maxTurningRadiusWheel = 0 |
2328 | for i, wheel in ipairs(spec.wheels) do |
2329 | if wheel.rotSpeed ~= 0 then |
2330 | local diffX, _, diffZ = localToLocal(wheel.node, spec.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ) |
2331 | local turningRadius = math.abs(diffZ)/math.tan(rotMax) + math.abs(diffX) |
2332 | if turningRadius >= maxTurningRadius then |
2333 | maxTurningRadius = turningRadius |
2334 | maxTurningRadiusWheel = i |
2335 | end |
2336 | end |
2337 | end |
2338 | self.maxRotation = math.max(Utils.getNoNil(self.maxRotation, 0), rotMax) |
2339 | self.maxTurningRadius = maxTurningRadius |
2340 | self.maxTurningRadiusWheel = maxTurningRadiusWheel |
2341 | self.wheelSteeringDuration = rotMax / rotSpeed |
2342 | |
2343 | if maxTurningRadiusWheel > 0 then |
2344 | for _, wheel in ipairs(spec.wheels) do |
2345 | |
2346 | if wheel.rotSpeed ~= 0 then |
2347 | local diffX, _, diffZ = localToLocal(wheel.node, spec.steeringCenterNode, wheel.positionX, wheel.positionY, wheel.positionZ) |
2348 | |
2349 | local rotMaxI = math.atan(diffZ/(maxTurningRadius-diffX)) |
2350 | local rotMinI = -math.atan(diffZ/(maxTurningRadius+diffX)) |
2351 | |
2352 | local switchMaxMin = rotMinI > rotMaxI |
2353 | if switchMaxMin then |
2354 | rotMaxI, rotMinI = rotMinI, rotMaxI |
2355 | end |
2356 | |
2357 | wheel.rotMax = rotMaxI |
2358 | wheel.rotMin = rotMinI |
2359 | wheel.rotSpeed = rotMaxI/self.wheelSteeringDuration |
2360 | wheel.rotSpeedNeg = -rotMinI/self.wheelSteeringDuration |
2361 | |
2362 | if wheel.steeringAxleScale ~= 0 then |
2363 | if switchMaxMin then |
2364 | wheel.steeringAxleScale = -wheel.steeringAxleScale |
2365 | end |
2366 | |
2367 | wheel.steeringAxleRotMax = rotMaxI |
2368 | wheel.steeringAxleRotMin = rotMinI |
2369 | end |
2370 | |
2371 | if wheel.invertRotLimit then |
2372 | switchMaxMin = not switchMaxMin |
2373 | end |
2374 | |
2375 | if switchMaxMin then |
2376 | wheel.rotSpeed, wheel.rotSpeedNeg = -wheel.rotSpeedNeg, -wheel.rotSpeed |
2377 | end |
2378 | end |
2379 | end |
2380 | end |
2381 | end |
2382 | end |
2383 | |
2384 | for _, wheel in ipairs(spec.wheels) do |
2385 | if wheel.rotSpeed ~= 0 then |
2386 | -- if both speed and rot have the same sign, we can reach it with the positive time |
2387 | if (wheel.rotMax >= 0) == (wheel.rotSpeed >= 0) then |
2388 | self.maxRotTime = math.max(wheel.rotMax/wheel.rotSpeed, self.maxRotTime) |
2389 | end |
2390 | if (wheel.rotMin >= 0) == (wheel.rotSpeed >= 0) then |
2391 | self.maxRotTime = math.max(wheel.rotMin/wheel.rotSpeed, self.maxRotTime) |
2392 | end |
2393 | |
2394 | -- if speed and rot have a different sign, we can reach it with the negative time |
2395 | local rotSpeedNeg = wheel.rotSpeedNeg |
2396 | if rotSpeedNeg == nil then |
2397 | rotSpeedNeg = wheel.rotSpeed |
2398 | end |
2399 | if (wheel.rotMax >= 0) ~= (rotSpeedNeg >= 0) then |
2400 | self.minRotTime = math.min(wheel.rotMax/rotSpeedNeg, self.minRotTime) |
2401 | end |
2402 | if (wheel.rotMin >= 0) ~= (rotSpeedNeg >= 0) then |
2403 | self.minRotTime = math.min(wheel.rotMin/rotSpeedNeg, self.minRotTime) |
2404 | end |
2405 | end |
2406 | |
2407 | for i=1, #wheel.fenders do |
2408 | local fender = wheel.fenders[i] |
2409 | |
2410 | fender.rotMax = fender.rotMax or wheel.rotMax |
2411 | fender.rotMin = fender.rotMin or wheel.rotMin |
2412 | end |
2413 | |
2414 | wheel.steeringNodeMaxRot = math.max(wheel.rotMax, wheel.steeringAxleRotMax) |
2415 | wheel.steeringNodeMinRot = math.min(wheel.rotMin, wheel.steeringAxleRotMin) |
2416 | |
2417 | if wheel.rotSpeedLimit ~= nil then |
2418 | wheel.rotSpeedDefault = wheel.rotSpeed |
2419 | wheel.rotSpeedNegDefault = wheel.rotSpeedNeg |
2420 | wheel.currentRotSpeedAlpha = 1 |
2421 | end |
2422 | end |
2423 | end |
1875 | function Wheels:loadAdditionalWheelConnectorFromXML(wheel, additionalWheel, xmlFile, configKey, wheelKey) |
1876 | local spec = self.spec_wheels |
1877 | |
1878 | local connectorFilename = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#filename") |
1879 | if connectorFilename ~= nil and connectorFilename ~= "" then |
1880 | XMLUtil.checkDeprecatedXMLElements(xmlFile, configKey..wheelKey..".connector#index", configKey..wheelKey..".connector#node") |
1881 | |
1882 | local connector = {} |
1883 | |
1884 | if connectorFilename:endsWith(".xml") then |
1885 | local xmlFilename = Utils.getFilename(connectorFilename, self.baseDirectory) |
1886 | local connectorXmlFile = XMLFile.load("connectorXml", xmlFilename, Wheels.xmlSchemaConnector) |
1887 | |
1888 | if connectorXmlFile ~= nil then |
1889 | local nodeKey = "leftNode" |
1890 | if not wheel.isLeft then |
1891 | nodeKey = "rightNode" |
1892 | end |
1893 | connector.filename = connectorXmlFile:getValue("connector.file#name") |
1894 | connector.nodeStr = connectorXmlFile:getValue("connector.file#"..nodeKey) |
1895 | |
1896 | connectorXmlFile:delete() |
1897 | else |
1898 | Logging.xmlError(xmlFile, "Unable to load connector xml file '%s'!", connectorFilename) |
1899 | end |
1900 | else |
1901 | connector.filename = connectorFilename |
1902 | connector.nodeStr = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#node") |
1903 | end |
1904 | |
1905 | if connector.filename ~= nil and connector.filename ~= "" then |
1906 | connector.useWidthAndDiam = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#useWidthAndDiam", false) |
1907 | connector.usePosAndScale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#usePosAndScale", false) |
1908 | |
1909 | connector.diameter = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#diameter") |
1910 | connector.additionalOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#offset", 0) |
1911 | connector.width = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#width") |
1912 | connector.startPos = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#startPos") |
1913 | connector.endPos = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#endPos") |
1914 | connector.scale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#uniformScale") |
1915 | connector.color = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".connector#color", nil, true) |
1916 | if connector.color == nil then |
1917 | connector.color = ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations["rimColor"]) or wheel.color or spec.rimColor |
1918 | else |
1919 | connector.color[4] = nil |
1920 | end |
1921 | |
1922 | additionalWheel.connector = connector |
1923 | end |
1924 | end |
1925 | end |
1819 | function Wheels:loadAdditionalWheelsFromXML(xmlFile, configKey, wheelKey, wheel) |
1820 | local additionalWheels = {} |
1821 | |
1822 | local i = 0 |
1823 | while true do |
1824 | local additionalWheelKey = string.format(wheelKey..".additionalWheel(%d)", i) |
1825 | if not xmlFile:hasProperty(configKey .. additionalWheelKey) then |
1826 | break |
1827 | end |
1828 | |
1829 | local xmlFilename = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#filename") |
1830 | if xmlFilename ~= nil and xmlFilename ~= "" then |
1831 | XMLUtil.checkDeprecatedXMLElements(xmlFile, configKey .. additionalWheelKey.."#configIndex", configKey .. additionalWheelKey.."#configId") |
1832 | XMLUtil.checkDeprecatedXMLElements(xmlFile, configKey .. additionalWheelKey.."#addRaycast", nil) |
1833 | |
1834 | local additionalWheel = {} |
1835 | additionalWheel.node = wheel.node |
1836 | additionalWheel.key = configKey .. additionalWheelKey |
1837 | additionalWheel.singleKey = additionalWheelKey |
1838 | additionalWheel.linkNode = wheel.linkNode |
1839 | |
1840 | local wheelConfigId = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#configId", "default") |
1841 | additionalWheel.isLeft = Utils.getNoNil(self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#isLeft", wheel.isLeft), false) |
1842 | additionalWheel.xRotOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#xRotOffset", 0) |
1843 | additionalWheel.color = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#color", nil, true) or wheel.color |
1844 | |
1845 | if self:loadWheelDataFromExternalXML(additionalWheel, xmlFilename, wheelConfigId, false) then |
1846 | additionalWheel.hasParticles = Utils.getNoNil(self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#hasParticles", wheel.hasParticles), false) |
1847 | additionalWheel.hasTireTracks = Utils.getNoNil(self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#hasTireTracks", wheel.hasTireTracks), false) |
1848 | |
1849 | additionalWheel.offset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, additionalWheelKey.."#offset", 0) |
1850 | |
1851 | self:loadWheelVisualData(xmlFile, configKey, additionalWheelKey, additionalWheel, wheel.configIndex) |
1852 | |
1853 | self:loadAdditionalWheelConnectorFromXML(wheel, additionalWheel, xmlFile, configKey, additionalWheelKey) |
1854 | |
1855 | table.insert(additionalWheels, additionalWheel) |
1856 | |
1857 | wheel.mass = wheel.mass + additionalWheel.mass + additionalWheel.additionalMass |
1858 | wheel.maxLatStiffness = wheel.maxLatStiffness + additionalWheel.maxLatStiffness |
1859 | wheel.maxLongStiffness = wheel.maxLongStiffness + additionalWheel.maxLongStiffness |
1860 | end |
1861 | end |
1862 | |
1863 | i = i + 1 |
1864 | end |
1865 | |
1866 | if #additionalWheels > 0 then |
1867 | wheel.additionalWheels = additionalWheels |
1868 | end |
1869 | |
1870 | return true |
1871 | end |
4251 | function Wheels.loadSpecValueWheelWeight(xmlFile, customEnvironment, baseDir) |
4252 | local configurationSaveIdToIndex, configurationIndexToBaseConfig = Wheels.createConfigSaveIdMapping(xmlFile) |
4253 | |
4254 | -- find default wheel config, either index 0 or the one where #isDefault attribute is set |
4255 | local defaultConfigIndex = 0 |
4256 | xmlFile:iterate("vehicle.wheels.wheelConfigurations.wheelConfiguration", function(configIndex, wheelConfigKey) |
4257 | if xmlFile:getValue(wheelConfigKey .. "#isDefault") then |
4258 | defaultConfigIndex = configIndex |
4259 | return false |
4260 | end |
4261 | end) |
4262 | |
4263 | local defaultConfigKey = string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration(%d)", defaultConfigIndex) |
4264 | local configMass = 0 |
4265 | |
4266 | xmlFile:iterate(defaultConfigKey .. ".wheels.wheel", function(index, key) |
4267 | local mass = Wheels.getConfigurationValue(configurationSaveIdToIndex, configurationIndexToBaseConfig, xmlFile, defaultConfigIndex, defaultConfigKey, string.format(".wheels.wheel(%d).physics#mass", index - 1)) |
4268 | |
4269 | if mass ~= nil then |
4270 | configMass = configMass + mass |
4271 | else |
4272 | local filename = Wheels.getConfigurationValue(configurationSaveIdToIndex, configurationIndexToBaseConfig, xmlFile, defaultConfigIndex, defaultConfigKey, string.format(".wheels.wheel(%d)#filename", index - 1)) |
4273 | if filename ~= nil then |
4274 | local wheelConfigId = Wheels.getConfigurationValue(configurationSaveIdToIndex, configurationIndexToBaseConfig, xmlFile, defaultConfigIndex, defaultConfigKey, string.format(".wheels.wheel(%d)#configId", index - 1)) |
4275 | filename = Utils.getFilename(filename, baseDir) |
4276 | |
4277 | configMass = configMass + Wheels.getWheelMassFromExternalFile(filename, wheelConfigId) |
4278 | else |
4279 | configMass = configMass + 0.1 -- default mass |
4280 | end |
4281 | |
4282 | xmlFile:iterate(defaultConfigKey .. string.format(".wheels.wheel(%d).additionalWheel", index - 1), function(additionalIndex, _) |
4283 | local additionalFilename = Wheels.getConfigurationValue(configurationSaveIdToIndex, configurationIndexToBaseConfig, xmlFile, defaultConfigIndex, defaultConfigKey, string.format(".wheels.wheel(%d).additionalWheel(%d)#filename", index - 1, additionalIndex - 1)) |
4284 | if additionalFilename ~= nil then |
4285 | local wheelConfigId = Wheels.getConfigurationValue(configurationSaveIdToIndex, configurationIndexToBaseConfig, xmlFile, defaultConfigIndex, defaultConfigKey, string.format(".wheels.wheel(%d).additionalWheel(%d)#configId", index - 1, additionalIndex - 1)) |
4286 | additionalFilename = Utils.getFilename(additionalFilename, baseDir) |
4287 | |
4288 | configMass = configMass + Wheels.getWheelMassFromExternalFile(additionalFilename, wheelConfigId) |
4289 | end |
4290 | end) |
4291 | end |
4292 | end) |
4293 | |
4294 | return configMass |
4295 | end |
1616 | function Wheels:loadWheelPhysicsData(xmlFile, configKey, wheelKey, wheel) |
1617 | wheel.node = self:getParentComponent(wheel.repr) |
1618 | if wheel.node ~= 0 then |
1619 | wheel.driveNode = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#driveNode", nil, self.components, self.i3dMappings) |
1620 | |
1621 | if wheel.driveNode == wheel.repr then |
1622 | Logging.xmlWarning(xmlFile, "repr and driveNode may not be equal for '%s'. Using default driveNode instead!", wheelKey) |
1623 | wheel.driveNode = nil |
1624 | end |
1625 | |
1626 | wheel.linkNode = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#linkNode", nil, self.components, self.i3dMappings) |
1627 | |
1628 | if wheel.driveNode == nil then |
1629 | -- create a new repr and use repr as drivenode |
1630 | local newRepr = createTransformGroup("wheelReprNode") |
1631 | local reprIndex = getChildIndex(wheel.repr) |
1632 | link(getParent(wheel.repr), newRepr, reprIndex) |
1633 | setTranslation(newRepr, getTranslation(wheel.repr)) |
1634 | setRotation(newRepr, getRotation(wheel.repr)) |
1635 | setScale(newRepr, getScale(wheel.repr)) |
1636 | wheel.driveNode = wheel.repr |
1637 | |
1638 | link(newRepr, wheel.driveNode) |
1639 | setTranslation(wheel.driveNode, 0, 0, 0) |
1640 | setRotation(wheel.driveNode, 0, 0, 0) |
1641 | setScale(wheel.driveNode, 1, 1, 1) |
1642 | wheel.repr = newRepr |
1643 | end |
1644 | |
1645 | if wheel.driveNode ~= nil then |
1646 | local driveNodeDirectionNode = createTransformGroup("driveNodeDirectionNode") |
1647 | link(getParent(wheel.repr), driveNodeDirectionNode) |
1648 | setWorldTranslation(driveNodeDirectionNode, getWorldTranslation(wheel.driveNode)) |
1649 | setWorldRotation(driveNodeDirectionNode, getWorldRotation(wheel.driveNode)) |
1650 | wheel.driveNodeDirectionNode = driveNodeDirectionNode |
1651 | |
1652 | local defaultX, defaultY, defaultZ = getRotation(wheel.driveNode) |
1653 | if math.abs(defaultX) > 0.0001 or math.abs(defaultY) > 0.0001 or math.abs(defaultZ) > 0.0001 then |
1654 | Logging.xmlWarning(xmlFile, "Rotation of driveNode '%s' is not 0/0/0 in the i3d file (%.1f/%.1f/%.1f). '%s'", getName(wheel.driveNode), math.deg(defaultX), math.deg(defaultY), math.deg(defaultZ), wheelKey) |
1655 | end |
1656 | end |
1657 | |
1658 | if wheel.linkNode == nil then |
1659 | wheel.linkNode = wheel.driveNode |
1660 | end |
1661 | |
1662 | wheel.yOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#yOffset", 0.0) |
1663 | if wheel.yOffset ~= 0 then |
1664 | -- move drivenode in y direction. Use convert yOffset from driveNode local space to driveNodeParent local space to translate according to directions |
1665 | setTranslation(wheel.driveNode, localToLocal(wheel.driveNode, getParent(wheel.driveNode), 0, wheel.yOffset, 0)) |
1666 | end |
1667 | |
1668 | wheel.showSteeringAngle = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#showSteeringAngle", true) |
1669 | wheel.suspTravel = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#suspTravel", 0.01) |
1670 | local initialCompression = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#initialCompression") |
1671 | if initialCompression ~= nil then |
1672 | wheel.deltaY = (1-initialCompression*0.01)*wheel.suspTravel |
1673 | else |
1674 | wheel.deltaY = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#deltaY", 0.0) |
1675 | end |
1676 | wheel.spring = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#spring", 0)*Vehicle.SPRING_SCALE |
1677 | |
1678 | wheel.torque = 0 |
1679 | wheel.brakeFactor = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#brakeFactor", 1) |
1680 | wheel.autoHoldBrakeFactor = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#autoHoldBrakeFactor", wheel.brakeFactor) |
1681 | |
1682 | wheel.damperCompressionLowSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperCompressionLowSpeed") |
1683 | wheel.damperRelaxationLowSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperRelaxationLowSpeed") |
1684 | if wheel.damperRelaxationLowSpeed == nil then |
1685 | wheel.damperRelaxationLowSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damper", wheel.damperCompressionLowSpeed or 0) |
1686 | end |
1687 | -- by default, the high speed relaxation damper is set to 90% of the low speed relaxation damper |
1688 | wheel.damperRelaxationHighSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperRelaxationHighSpeed", wheel.damperRelaxationLowSpeed * 0.7) |
1689 | |
1690 | -- by default, we set the low speed compression damper to 90% of the low speed relaxation damper |
1691 | if wheel.damperCompressionLowSpeed == nil then |
1692 | wheel.damperCompressionLowSpeed = wheel.damperRelaxationLowSpeed * 0.9 |
1693 | end |
1694 | -- by default, the high speed compression damper is set to 20% of the low speed compression damper |
1695 | wheel.damperCompressionHighSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperCompressionHighSpeed", wheel.damperCompressionLowSpeed * 0.2) |
1696 | wheel.damperCompressionLowSpeedThreshold = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperCompressionLowSpeedThreshold", 0.1016) -- default 4 inch / s |
1697 | wheel.damperRelaxationLowSpeedThreshold = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#damperRelaxationLowSpeedThreshold", 0.1524) -- default 6 inch / s |
1698 | |
1699 | wheel.forcePointRatio = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#forcePointRatio", 0) |
1700 | wheel.driveMode = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#driveMode", 0) |
1701 | wheel.xOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#xOffset", 0) |
1702 | |
1703 | |
1704 | wheel.transRatio = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#transRatio", 0.0) |
1705 | |
1706 | wheel.isSynchronized = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#isSynchronized", true) |
1707 | wheel.tipOcclusionAreaGroupId = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#tipOcclusionAreaGroupId") |
1708 | |
1709 | wheel.positionX, wheel.positionY, wheel.positionZ = localToLocal(wheel.driveNode, wheel.node, 0, 0, 0) |
1710 | wheel.useReprDirection = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#useReprDirection", false) |
1711 | wheel.useDriveNodeDirection = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#useDriveNodeDirection", false) |
1712 | |
1713 | |
1714 | wheel.mass = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#mass", wheel.mass) |
1715 | wheel.radius = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#radius", wheel.radius or 0.5) |
1716 | wheel.width = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#width", wheel.width or 0.6) |
1717 | wheel.wheelShapeWidth = wheel.width |
1718 | wheel.widthOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#widthOffset", 0) |
1719 | wheel.restLoad = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#restLoad", wheel.restLoad or 1.0) -- [t] |
1720 | wheel.maxLongStiffness = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#maxLongStiffness", wheel.maxLongStiffness or 30.0) -- [t / rad] |
1721 | wheel.maxLatStiffness = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#maxLatStiffness", wheel.maxLatStiffness or 40.0) -- xml is ratio to restLoad [1/rad], final value is [t / rad] |
1722 | wheel.maxLatStiffnessLoad = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#maxLatStiffnessLoad", wheel.maxLatStiffnessLoad or 2) -- xml is ratio to restLoad, final value is [t] |
1723 | wheel.frictionScale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#frictionScale", wheel.frictionScale or 1.0) |
1724 | wheel.rotationDamping = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotationDamping", wheel.mass * 0.035) |
1725 | wheel.tireGroundFrictionCoeff = 1.0 -- This will be changed dynamically based on the tire-ground pair |
1726 | |
1727 | local tireTypeName = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#tireType", "mud") |
1728 | wheel.tireType = WheelsUtil.getTireType(tireTypeName) |
1729 | if wheel.tireType == nil then |
1730 | Logging.xmlWarning(xmlFile, "Failed to find tire type '%s'. Defaulting to 'mud'!", tireTypeName) |
1731 | wheel.tireType = WheelsUtil.getTireType("mud") |
1732 | end |
1733 | |
1734 | local maxWheelSinkDefault = 0.5 |
1735 | if wheel.tireType == WheelsUtil.getTireType("crawler") then |
1736 | -- crawlers barely sink + avoid clipping of terrain detail with belt shape |
1737 | maxWheelSinkDefault = 0.01 |
1738 | end |
1739 | |
1740 | wheel.fieldDirtMultiplier = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#fieldDirtMultiplier", 75) |
1741 | wheel.streetDirtMultiplier = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#streetDirtMultiplier", -150) |
1742 | wheel.minDirtPercentage = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#minDirtPercentage", 0.35) |
1743 | wheel.maxDirtOffset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#maxDirtOffset", 0.5) |
1744 | wheel.dirtColorChangeSpeed = 1 / (self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#dirtColorChangeSpeed", 20) * 1000) |
1745 | |
1746 | wheel.smoothGroundRadius = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#smoothGroundRadius", wheel.smoothGroundRadius or math.max(0.6, wheel.width*0.75)) |
1747 | wheel.versatileYRot = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#versatileYRot", false) |
1748 | wheel.forceVersatility = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#forceVersatility", false) |
1749 | wheel.supportsWheelSink = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#supportsWheelSink", true) |
1750 | wheel.maxWheelSink = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#maxWheelSink", maxWheelSinkDefault) |
1751 | |
1752 | wheel.rotSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotSpeed", 0) |
1753 | wheel.rotSpeedNeg = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotSpeedNeg", 0) |
1754 | wheel.rotMax = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotMax", 0) |
1755 | wheel.rotMin = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotMin", 0) |
1756 | |
1757 | wheel.invertRotLimit = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#invertRotLimit", false) |
1758 | wheel.rotSpeedLimit = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey .. ".physics#rotSpeedLimit") |
1759 | else |
1760 | Logging.xmlWarning(xmlFile, "Invalid repr for wheel '%s'. Needs to be a child of a collision!", configKey .. wheelKey) |
1761 | return false |
1762 | end |
1763 | |
1764 | return true |
1765 | end |
1514 | function Wheels:loadWheelSharedData(xmlFile, configKey, wheelKey, wheel, skipConfigurations) |
1515 | local configIndex = wheel.configIndex |
1516 | if skipConfigurations == true then |
1517 | configIndex = -1 |
1518 | end |
1519 | |
1520 | local key = "nodeLeft" |
1521 | if not wheel.isLeft then |
1522 | key = "nodeRight" |
1523 | end |
1524 | |
1525 | wheel.radius = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey..".physics#radius", wheel.radius) |
1526 | if wheel.radius == nil then |
1527 | Logging.xmlWarning(xmlFile, "No radius defined for wheel '%s'! Using default value of 0.5!", configKey .. wheelKey..".physics#radius") |
1528 | wheel.radius = 0.5 |
1529 | end |
1530 | wheel.width = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey..".physics#width", wheel.width) |
1531 | if wheel.width == nil then |
1532 | Logging.xmlWarning(xmlFile, "No width defined for wheel '%s'! Using default value of 0.5!", configKey .. wheelKey..".physics#width") |
1533 | wheel.width = 0.5 |
1534 | end |
1535 | wheel.mass = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey..".physics#mass", wheel.mass or 0.1) |
1536 | |
1537 | local tireTypeName = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey..".physics#tireType") |
1538 | if tireTypeName ~= nil then |
1539 | local tireType = WheelsUtil.getTireType(tireTypeName) |
1540 | if tireType ~= nil then |
1541 | wheel.tireType = tireType |
1542 | else |
1543 | Logging.xmlWarning(xmlFile, "Tire type '%s' not defined!", tireTypeName) |
1544 | end |
1545 | end |
1546 | |
1547 | wheel.frictionScale = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".physics#frictionScale", wheel.frictionScale) |
1548 | wheel.maxLongStiffness = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".physics#maxLongStiffness", wheel.maxLongStiffness) -- [t / rad] |
1549 | wheel.maxLatStiffness = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".physics#maxLatStiffness", wheel.maxLatStiffness) -- xml is ratio to restLoad [1/rad], final value is [t / rad] |
1550 | wheel.maxLatStiffnessLoad = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".physics#maxLatStiffnessLoad", wheel.maxLatStiffnessLoad) -- xml is ratio to restLoad, final value is [t] |
1551 | |
1552 | wheel.tireTrackAtlasIndex = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#tireTrackAtlasIndex", wheel.tireTrackAtlasIndex or 0) |
1553 | wheel.widthOffset = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#widthOffset", wheel.widthOffset or 0.0) |
1554 | wheel.xOffset = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#xOffset", wheel.xOffset or 0) |
1555 | wheel.maxDeformation = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#maxDeformation", wheel.maxDeformation or 0) |
1556 | wheel.initialDeformation = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#initialDeformation", wheel.initialDeformation or math.min(0.04, wheel.maxDeformation * 0.6)) |
1557 | wheel.sideDeformOffset = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#sideDeformOffset", wheel.sideDeformOffset or 1.0) |
1558 | |
1559 | wheel.deformation = 0 |
1560 | wheel.isCareWheel = Utils.getNoNil(self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#isCareWheel", wheel.isCareWheel), true) |
1561 | wheel.smoothGroundRadius = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".physics#smoothGroundRadius", math.max(0.6, wheel.width*0.75)) |
1562 | |
1563 | self:loadWheelVisualData(xmlFile, configKey, wheelKey, wheel, configIndex) |
1564 | |
1565 | return true |
1566 | end |
1575 | function Wheels:loadWheelVisualData(xmlFile, configKey, wheelKey, wheel, configIndex) |
1576 | local key = "nodeLeft" |
1577 | if not wheel.isLeft then |
1578 | key = "nodeRight" |
1579 | end |
1580 | |
1581 | wheel.tireFilename = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#filename", wheel.tireFilename) |
1582 | wheel.tireIsInverted = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#isInverted", wheel.tireIsInverted) |
1583 | wheel.tireNodeStr = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#node") |
1584 | or self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".tire#"..key, wheel.tireNodeStr) |
1585 | |
1586 | wheel.outerRimFilename = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".outerRim#filename", wheel.outerRimFilename) |
1587 | wheel.outerRimNodeStr = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".outerRim#node", nil) |
1588 | or self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".outerRim#"..key, wheel.outerRimNodeStr) |
1589 | or "0|0" |
1590 | wheel.outerRimWidthAndDiam = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".outerRim#widthAndDiam", wheel.outerRimWidthAndDiam, true) |
1591 | wheel.outerRimScale = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".outerRim#scale", wheel.outerRimScale, true) |
1592 | |
1593 | wheel.innerRimFilename = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#filename", wheel.innerRimFilename) |
1594 | wheel.innerRimNodeStr = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#node", nil) |
1595 | or self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#"..key, wheel.innerRimNodeStr) |
1596 | wheel.innerRimWidthAndDiam = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#widthAndDiam", wheel.innerRimWidthAndDiam, true) |
1597 | wheel.innerRimOffset = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#offset", wheel.innerRimOffset) or 0 |
1598 | wheel.innerRimScale = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".innerRim#scale", wheel.innerRimScale, true) |
1599 | |
1600 | wheel.additionalFilename = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#filename", wheel.additionalFilename) |
1601 | wheel.additionalNodeStr = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#node", nil) |
1602 | or self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#"..key, wheel.additionalNodeStr) |
1603 | wheel.additionalOffset = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#offset", wheel.additionalOffset) or 0 |
1604 | wheel.additionalScale = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#scale", wheel.additionalScale, true) |
1605 | wheel.additionalMass = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#mass", wheel.additionalMass) or 0 |
1606 | wheel.additionalWidthAndDiam = self:getWheelConfigurationValue(xmlFile, configIndex, configKey, wheelKey .. ".additional#widthAndDiam", wheel.additionalWidthAndDiam, true) |
1607 | end |
2842 | function Wheels:onAdditionalWheelConnectorI3DLoaded(i3dNode, failedReason, args) |
2843 | if i3dNode ~= 0 then |
2844 | local wheel = args.wheel |
2845 | local connector = args.connector |
2846 | local diameter = args.diameter |
2847 | local baseWheelWidth = args.baseWheelWidth |
2848 | local wheelDistance = args.wheelDistance |
2849 | local offsetDir = args.offsetDir |
2850 | local dualWheelWidth = args.dualWheelWidth |
2851 | local filename = args.filename |
2852 | |
2853 | local node = I3DUtil.indexToObject(i3dNode, connector.nodeStr, self.i3dMappings) |
2854 | if node ~= nil then |
2855 | connector.node = node |
2856 | connector.linkNode = wheel.wheelTire |
2857 | connector.filename = filename |
2858 | |
2859 | link(wheel.driveNode, connector.node) |
2860 | |
2861 | if not connector.useWidthAndDiam then |
2862 | if getHasShaderParameter(connector.node, "connectorPos") then |
2863 | I3DUtil.setShaderParameterRec(connector.node, "connectorPos", 0, baseWheelWidth, wheelDistance, dualWheelWidth, false) |
2864 | end |
2865 | |
2866 | local x,_,z,w = I3DUtil.getShaderParameterRec(connector.node, "widthAndDiam") |
2867 | I3DUtil.setShaderParameterRec(connector.node, "widthAndDiam", x, diameter, z, w, false) |
2868 | else |
2869 | local connectorOffset = offsetDir*(((0.5*baseWheelWidth + 0.5*wheelDistance) * 0.0254) + connector.additionalOffset) -- in meters |
2870 | local connectorDiameter = connector.diameter or diameter |
2871 | setTranslation(connector.node, connectorOffset, 0, 0) |
2872 | I3DUtil.setShaderParameterRec(connector.node, "widthAndDiam", connector.width, connectorDiameter, 0, 0, false) |
2873 | end |
2874 | if connector.usePosAndScale and getHasShaderParameter(connector.node, "connectorPosAndScale") then |
2875 | local _,_,_,w = I3DUtil.getShaderParameterRec(connector.node, "connectorPosAndScale") |
2876 | I3DUtil.setShaderParameterRec(connector.node, "connectorPosAndScale", connector.startPos, connector.endPos, connector.scale, w, false) |
2877 | end |
2878 | if connector.color ~= nil and getHasShaderParameter(connector.node, "colorMat0") then |
2879 | local r, g, b, mat1 = unpack(connector.color) |
2880 | local _, _, _, mat2 = I3DUtil.getShaderParameterRec(connector.node, "colorMat0") |
2881 | I3DUtil.setShaderParameterRec(connector.node, "colorMat0", r, g, b, mat1 or mat2, false) |
2882 | end |
2883 | end |
2884 | |
2885 | delete(i3dNode) |
2886 | end |
2887 | end |
542 | function Wheels:onLoad(savegame) |
543 | local spec = self.spec_wheels |
544 | |
545 | spec.sharedLoadRequestIds = {} |
546 | |
547 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.driveGroundParticleSystems", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#hasParticles") --FS13 to FS15 |
548 | |
549 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheelConfigurations.wheelConfiguration", "vehicle.wheels.wheelConfigurations.wheelConfiguration") --FS17 to FS19 |
550 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.rimColor", "vehicle.wheels.rimColor") --FS17 to FS19 |
551 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.hubColor", "vehicle.wheels.hubs.color0") --FS17 to FS19 |
552 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.dynamicallyLoadedWheels", "vehicle.wheels.dynamicallyLoadedWheels") --FS17 to FS19 |
553 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.ackermannSteeringConfigurations", "vehicle.wheels.ackermannSteeringConfigurations") --FS17 to FS19 |
554 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheels.wheel", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel") --FS17 to FS19 |
555 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
556 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheelConfigurations.wheelConfiguration.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
557 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#repr", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel.physics#repr") --FS17 to FS19 |
558 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#configIndex", "vehicle.wheels.wheelConfigurations.wheelConfiguration.wheels.wheel#configId") --FS17 to FS19 |
559 | |
560 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, "vehicle.ackermannSteering", "vehicle.wheels.ackermannSteeringConfigurations.ackermannSteering") --FS19 to FS21 |
561 | |
562 | spec.configurationSaveIdToIndex, spec.configurationIndexToBaseConfig = Wheels.createConfigSaveIdMapping(self.xmlFile) |
563 | spec.lastWheelConfigIndex = self.configurations["wheel"] |
564 | |
565 | -- wheel setup |
566 | local wheelConfigurationId = self.configurations["wheel"] or 1 |
567 | local configKey = string.format("vehicle.wheels.wheelConfigurations.wheelConfiguration(%d)", wheelConfigurationId - 1) |
568 | local wheelsKey = configKey .. ".wheels" |
569 | if self.configurations["wheel"] ~= nil and not self.xmlFile:hasProperty(wheelsKey) then |
570 | Logging.xmlWarning(self.xmlFile, "Invalid wheelConfigurationId '%d'. Using default wheel config instead!", self.configurations["wheel"]) |
571 | |
572 | -- reset to wheel config 1 to ensure wheels are present |
573 | wheelConfigurationId = 1 |
574 | configKey = "vehicle.wheels.wheelConfigurations.wheelConfiguration(0)" |
575 | wheelsKey = configKey .. ".wheels" |
576 | end |
577 | ObjectChangeUtil.updateObjectChanges(self.xmlFile, "vehicle.wheels.wheelConfigurations.wheelConfiguration", wheelConfigurationId , self.components, self) |
578 | |
579 | -- load configuration independent settings |
580 | spec.rimColor = self.xmlFile:getValue("vehicle.wheels.rimColor", nil, true) |
581 | if spec.rimColor == nil then |
582 | if self.xmlFile:getValue("vehicle.wheels.rimColor#useBaseColor") then |
583 | spec.rimColor = ConfigurationUtil.getColorByConfigId(self, "baseColor", self.configurations["baseColor"]) or ConfigurationUtil.getColorByConfigId(self, "baseMaterial", self.configurations["baseMaterial"]) |
584 | end |
585 | end |
586 | |
587 | if spec.rimColor ~= nil then |
588 | -- overwrite material from color string with nil unless explicitly defined in material attribute |
589 | spec.rimColor[4] = self.xmlFile:getValue("vehicle.wheels.rimColor#material") |
590 | end |
591 | |
592 | -- load overwritten rim and hub colors from material definitions in all configurations |
593 | spec.overwrittenWheelColors = {} |
594 | spec.overwrittenWheelColors["_rimColor"] = {} |
595 | for i=1, 8 do |
596 | spec.overwrittenWheelColors[string.format("_hubColor%d", i - 1)] = {} |
597 | end |
598 | ConfigurationUtil.getOverwrittenMaterialColors(self, self.xmlFile, spec.overwrittenWheelColors) |
599 | |
600 | local overwrittenRimColor = spec.overwrittenWheelColors["_rimColor"] |
601 | if #overwrittenRimColor > 0 then |
602 | if spec.rimColor == nil then |
603 | spec.rimColor = overwrittenRimColor |
604 | else |
605 | spec.rimColor[1], spec.rimColor[2], spec.rimColor[3] = overwrittenRimColor[1], overwrittenRimColor[2], overwrittenRimColor[3] |
606 | if #overwrittenRimColor == 4 then |
607 | spec.rimColor[4] = overwrittenRimColor[4] |
608 | end |
609 | end |
610 | end |
611 | |
612 | -- load hubs to hubs/repr nodes |
613 | self:loadHubsFromXML() |
614 | |
615 | self.maxRotTime = 0 |
616 | self.minRotTime = 0 |
617 | self.rotatedTimeInterpolator = InterpolatorValue.new(0) |
618 | |
619 | self.autoRotateBackSpeed = self:getWheelConfigurationValue(self.xmlFile, wheelConfigurationId, configKey, ".wheels#autoRotateBackSpeed", 1.0) |
620 | self.speedDependentRotateBack = self:getWheelConfigurationValue(self.xmlFile, wheelConfigurationId, configKey, ".wheels#speedDependentRotateBack", true) |
621 | self.differentialIndex = self:getWheelConfigurationValue(self.xmlFile, wheelConfigurationId, configKey, ".wheels#differentialIndex") -- needed by Drivable |
622 | spec.ackermannSteeringIndex = self:getWheelConfigurationValue(self.xmlFile, wheelConfigurationId, configKey, ".wheels#ackermannSteeringIndex") |
623 | |
624 | spec.wheelSmoothAccumulation = 0 |
625 | |
626 | spec.wheelCreationTimer = 0 |
627 | spec.currentUpdateIndex = 1 |
628 | spec.maxUpdateIndex = 1 |
629 | |
630 | spec.wheels = {} |
631 | spec.wheelsByNode = {} |
632 | spec.wheelChocks = {} |
633 | |
634 | spec.tireTrackNodes = {} |
635 | |
636 | -- load wheels |
637 | self:loadWheelsFromXML(self.xmlFile, configKey, wheelConfigurationId) |
638 | |
639 | --load surface sounds |
640 | if self.xmlFile:getValue(wheelsKey.."#hasSurfaceSounds", true) then |
641 | local surfaceSoundLinkNode = self.xmlFile:getValue(wheelsKey .. "#surfaceSoundLinkNode", self.components[1].node, self.components, self.i3dMappings) |
642 | |
643 | local tireTypeName = "" |
644 | if #spec.wheels > 0 and spec.wheels[1].tireType ~= nil then |
645 | tireTypeName = WheelsUtil.getTireTypeName(spec.wheels[1].tireType) |
646 | end |
647 | tireTypeName = self.xmlFile:getValue(wheelsKey.."#surfaceSoundTireType", tireTypeName) |
648 | |
649 | spec.surfaceSounds = {} |
650 | spec.surfaceIdToSound = {} |
651 | spec.surfaceNameToSound = {} |
652 | spec.currentSurfaceSound = nil |
653 | |
654 | local function addSurfaceSound(surfaceSound) |
655 | local sample = g_soundManager:cloneSample(surfaceSound.sample, surfaceSoundLinkNode, self) |
656 | sample.sampleName = surfaceSound.name |
657 | |
658 | table.insert(spec.surfaceSounds, sample) |
659 | spec.surfaceIdToSound[surfaceSound.materialId] = sample |
660 | spec.surfaceNameToSound[surfaceSound.name] = sample |
661 | end |
662 | |
663 | local surfaceSounds = g_currentMission.surfaceSounds |
664 | for j=1, #surfaceSounds do |
665 | local surfaceSound = surfaceSounds[j] |
666 | if surfaceSound.type:lower() == ("wheel_" .. tireTypeName):lower() then |
667 | addSurfaceSound(surfaceSound) |
668 | end |
669 | end |
670 | |
671 | for j=1, #surfaceSounds do |
672 | local surfaceSound = surfaceSounds[j] |
673 | if spec.surfaceNameToSound[surfaceSound.name] == nil then |
674 | if surfaceSound.type == "wheel" then |
675 | addSurfaceSound(surfaceSound) |
676 | end |
677 | end |
678 | end |
679 | end |
680 | |
681 | -- load non physical wheels |
682 | spec.dynamicallyLoadedWheels = {} |
683 | local i = 0 |
684 | while true do |
685 | local baseName = string.format("vehicle.wheels.dynamicallyLoadedWheels.dynamicallyLoadedWheel(%d)", i) |
686 | if not self.xmlFile:hasProperty(baseName) then |
687 | break |
688 | end |
689 | |
690 | XMLUtil.checkDeprecatedXMLElements(self.xmlFile, baseName .. "#configIndex", baseName .. "#configId") --FS17 to FS19 |
691 | |
692 | local dynamicallyLoadedWheel = {} |
693 | if self:loadNonPhysicalWheelFromXML(dynamicallyLoadedWheel, self.xmlFile, baseName) then |
694 | table.insert(spec.dynamicallyLoadedWheels, dynamicallyLoadedWheel) |
695 | end |
696 | |
697 | i = i + 1 |
698 | end |
699 | |
700 | spec.networkTimeInterpolator = InterpolationTime.new(1.2) |
701 | |
702 | -- find opposite wheel |
703 | local numWheels = #spec.wheels |
704 | for iWheel=1,numWheels do |
705 | local wheel1 = spec.wheels[iWheel] |
706 | if wheel1.oppositeWheelIndex == nil then |
707 | for jWheel=1,numWheels do |
708 | if iWheel ~= jWheel then |
709 | local wheel2 = spec.wheels[jWheel] |
710 | 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 |
711 | wheel1.oppositeWheelIndex = jWheel |
712 | wheel2.oppositeWheelIndex = iWheel |
713 | break |
714 | end |
715 | end |
716 | end |
717 | end |
718 | end |
719 | |
720 | -- |
721 | self:loadAckermannSteeringFromXML(self.xmlFile, spec.ackermannSteeringIndex) |
722 | |
723 | |
724 | SpecializationUtil.raiseEvent(self, "onFinishedWheelLoading", self.xmlFile, wheelsKey) |
725 | |
726 | spec.wheelSinkActive = Platform.gameplay.wheelSink |
727 | spec.wheelDensityHeightSmoothActive = Platform.gameplay.wheelDensityHeightSmooth |
728 | spec.wheelVisualPressureActive = Platform.gameplay.wheelVisualPressure |
729 | |
730 | spec.tyreTracksSegmentsCoeff = getTyreTracksSegmentsCoeff() |
731 | |
732 | spec.snowSystem = g_currentMission.snowSystem |
733 | spec.fieldGroundSystem = g_currentMission.fieldGroundSystem |
734 | spec.tireTrackGroundGrassValue = spec.fieldGroundSystem:getFieldGroundValue(FieldGroundType.GRASS) |
735 | spec.tireTrackGroundGrassCutValue = spec.fieldGroundSystem:getFieldGroundValue(FieldGroundType.GRASS_CUT) |
736 | |
737 | spec.brakePedal = 0 |
738 | spec.forceIsActiveTime = 3000 |
739 | spec.forceIsActiveTimer = 0 |
740 | spec.dirtyFlag = self:getNextDirtyFlag() |
741 | |
742 | g_messageCenter:subscribe(MessageType.SNOW_HEIGHT_CHANGED, self.onWheelSnowHeightChanged, self) |
743 | end |
1004 | function Wheels:onUpdate(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
1005 | local spec = self.spec_wheels |
1006 | |
1007 | if self.isServer then |
1008 | if spec.wheelCreationTimer > 0 then |
1009 | spec.wheelCreationTimer = spec.wheelCreationTimer - 1 |
1010 | if spec.wheelCreationTimer == 0 then |
1011 | for _,wheel in pairs(spec.wheels) do |
1012 | wheel.wheelShapeCreated = true |
1013 | end |
1014 | end |
1015 | end |
1016 | end |
1017 | |
1018 | -- interpolation of wheel properties |
1019 | if not self.isServer and self.isClient then |
1020 | spec.networkTimeInterpolator:update(dt) |
1021 | local interpolationAlpha = spec.networkTimeInterpolator:getAlpha() |
1022 | |
1023 | self.rotatedTime = self.rotatedTimeInterpolator:getInterpolatedValue(interpolationAlpha) |
1024 | |
1025 | for i=1, table.getn(spec.wheels) do |
1026 | local wheel = spec.wheels[i] |
1027 | wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z = wheel.networkInterpolators.position:getInterpolatedValues(interpolationAlpha) |
1028 | wheel.netInfo.xDrive = wheel.networkInterpolators.xDrive:getInterpolatedValue(interpolationAlpha) |
1029 | wheel.netInfo.suspensionLength = wheel.networkInterpolators.suspensionLength:getInterpolatedValue(interpolationAlpha) |
1030 | |
1031 | if wheel.driveGroundParticleSystems ~= nil then |
1032 | for _, typedPs in pairs(wheel.driveGroundParticleSystems) do |
1033 | for _, ps in ipairs(typedPs) do |
1034 | setTranslation(ps.emitterShape, wheel.netInfo.x + ps.offsets[1], wheel.netInfo.y + ps.offsets[2], wheel.netInfo.z + ps.offsets[3]) |
1035 | end |
1036 | end |
1037 | end |
1038 | end |
1039 | |
1040 | if spec.networkTimeInterpolator:isInterpolating() then |
1041 | self:raiseActive() |
1042 | end |
1043 | end |
1044 | |
1045 | local numWheels = #spec.wheels |
1046 | if self.finishedFirstUpdate then |
1047 | local groundWetness = g_currentMission.environment.weather:getGroundWetness() |
1048 | |
1049 | for i=1, numWheels do |
1050 | local wheel = spec.wheels[i] |
1051 | |
1052 | if self.isActive then |
1053 | if spec.currentUpdateIndex == wheel.updateIndex then |
1054 | self:updateWheelContact(wheel) |
1055 | end |
1056 | |
1057 | if spec.wheelSinkActive then |
1058 | self:updateWheelSink(wheel, dt, groundWetness) |
1059 | end |
1060 | self:updateWheelFriction(wheel, dt, groundWetness) |
1061 | if spec.wheelDensityHeightSmoothActive then |
1062 | self:updateWheelDensityMapHeight(wheel, dt) |
1063 | end |
1064 | |
1065 | WheelsUtil.updateWheelPhysics(self, wheel, spec.brakePedal, dt) |
1066 | end |
1067 | |
1068 | if self.isServer and self.isAddedToPhysics then |
1069 | WheelsUtil.updateWheelNetInfo(self, wheel) |
1070 | end |
1071 | |
1072 | if self.currentUpdateDistance < Wheels.VISUAL_WHEEL_UPDATE_DISTANCE then |
1073 | local changed = WheelsUtil.updateWheelGraphics(self, wheel, dt) |
1074 | if wheel.updateWheelChock and changed then |
1075 | for j=1, #wheel.wheelChocks do |
1076 | self:updateWheelChockPosition(wheel.wheelChocks[j], false) |
1077 | end |
1078 | end |
1079 | end |
1080 | end |
1081 | |
1082 | spec.currentUpdateIndex = spec.currentUpdateIndex + 1 |
1083 | if spec.currentUpdateIndex > spec.maxUpdateIndex then |
1084 | spec.currentUpdateIndex = 1 |
1085 | end |
1086 | |
1087 | if self.isActive then |
1088 | local numTireTrackNodes = #spec.tireTrackNodes |
1089 | if numTireTrackNodes > 0 then |
1090 | local allowTireTracks = self:getAllowTireTracks() |
1091 | for i=1, numTireTrackNodes do |
1092 | local tireTrackNode = spec.tireTrackNodes[i] |
1093 | self:updateTireTrackNode(tireTrackNode, allowTireTracks, groundWetness) |
1094 | end |
1095 | end |
1096 | |
1097 | if numWheels > 0 then |
1098 | if g_currentMission.missionInfo.fruitDestruction |
1099 | and not self:getIsAIActive() |
1100 | and (self.getBlockFoliageDestruction == nil or not self:getBlockFoliageDestruction()) then |
1101 | |
1102 | for i=1, numWheels do |
1103 | local wheel = spec.wheels[i] |
1104 | self:updateWheelDestruction(wheel, dt) |
1105 | end |
1106 | end |
1107 | end |
1108 | end |
1109 | |
1110 | if self:getAreSurfaceSoundsActive() then |
1111 | -- update surface sounds |
1112 | if spec.surfaceSounds ~= nil then |
1113 | local currentSound = self:getCurrentSurfaceSound() |
1114 | if currentSound ~= spec.currentSurfaceSound then |
1115 | if spec.currentSurfaceSound ~= nil then |
1116 | g_soundManager:stopSample(spec.currentSurfaceSound) |
1117 | end |
1118 | if currentSound ~= nil then |
1119 | g_soundManager:playSample(currentSound) |
1120 | end |
1121 | |
1122 | spec.currentSurfaceSound = currentSound |
1123 | else |
1124 | if not g_soundManager:getIsSamplePlaying(currentSound) then |
1125 | g_soundManager:playSample(currentSound) |
1126 | end |
1127 | end |
1128 | end |
1129 | else |
1130 | if spec.currentSurfaceSound ~= nil then |
1131 | g_soundManager:stopSample(spec.currentSurfaceSound) |
1132 | end |
1133 | end |
1134 | end |
1135 | |
1136 | if numWheels > 0 then |
1137 | if self.isServer then |
1138 | self:raiseDirtyFlags(spec.dirtyFlag) |
1139 | end |
1140 | end |
1141 | end |
1168 | function Wheels:onUpdateTick(dt, isActiveForInput, isActiveForInputIgnoreSelection, isSelected) |
1169 | local spec = self.spec_wheels |
1170 | |
1171 | for _, wheel in pairs(spec.wheels) do |
1172 | if wheel.rotSpeedLimit ~= nil then |
1173 | local dir = -1 |
1174 | if self:getLastSpeed() <= wheel.rotSpeedLimit then |
1175 | dir = 1 |
1176 | end |
1177 | |
1178 | wheel.currentRotSpeedAlpha = MathUtil.clamp(wheel.currentRotSpeedAlpha + dir*(dt/1000), 0, 1) |
1179 | wheel.rotSpeed = wheel.rotSpeedDefault * wheel.currentRotSpeedAlpha |
1180 | wheel.rotSpeedNeg = wheel.rotSpeedNegDefault * wheel.currentRotSpeedAlpha |
1181 | end |
1182 | end |
1183 | |
1184 | if self.isClient then |
1185 | local speed = self:getLastSpeed() |
1186 | local groundWetness = g_currentMission.environment.weather:getGroundWetness() |
1187 | local groundIsWet = groundWetness > 0.2 |
1188 | |
1189 | for _,wheel in pairs(spec.wheels) do |
1190 | if wheel.driveGroundParticleSystems ~= nil then |
1191 | local states = wheel.driveGroundParticleStates |
1192 | local enableSoilPS = false |
1193 | if wheel.lastTerrainValue > 0 and wheel.lastTerrainValue < 9 then |
1194 | enableSoilPS = (speed > 1) -- and wheel.sink > 0 |
1195 | end |
1196 | |
1197 | local sizeScale = 2 * wheel.width * wheel.radiusOriginal |
1198 | |
1199 | states.wheel_dry = not wheel.hasSnowContact and enableSoilPS |
1200 | states.wheel_wet = not wheel.hasSnowContact and enableSoilPS and groundIsWet |
1201 | states.wheel_dust = not wheel.hasSnowContact and not groundIsWet |
1202 | states.wheel_snow = wheel.hasSnowContact |
1203 | |
1204 | for psName, state in pairs(states) do |
1205 | local typedPs = wheel.driveGroundParticleSystems[psName] |
1206 | if typedPs ~= nil then |
1207 | for _, ps in ipairs(typedPs) do |
1208 | if state then |
1209 | if self.movingDirection < 0 then |
1210 | setRotation(ps.emitterShape, 0, math.pi+wheel.steeringAngle, 0) |
1211 | else |
1212 | setRotation(ps.emitterShape, 0, wheel.steeringAngle, 0) |
1213 | end |
1214 | |
1215 | local scale |
1216 | if psName ~= "wheel_dust" then |
1217 | local wheelSpeed = MathUtil.rpmToMps(wheel.netInfo.xDriveSpeed / (2*math.pi) * 60, wheel.radius) |
1218 | local wheelSlip = math.pow(wheelSpeed/self.lastSpeedReal, 2.5) |
1219 | scale = self:getDriveGroundParticleSystemsScale(ps, wheelSpeed) * wheelSlip |
1220 | else |
1221 | scale = self:getDriveGroundParticleSystemsScale(ps, self.lastSpeedReal) |
1222 | end |
1223 | |
1224 | if ps.isTintable then |
1225 | -- interpolate between different ground colors to avoid unrealisitic particle color changes |
1226 | if ps.lastColor == nil then |
1227 | ps.lastColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1228 | ps.targetColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1229 | ps.currentColor = {ps.wheel.lastColor[1],ps.wheel.lastColor[2],ps.wheel.lastColor[3]} |
1230 | ps.alpha = 1 |
1231 | end |
1232 | |
1233 | if ps.alpha ~= 1 then |
1234 | ps.alpha = math.min(ps.alpha + dt/1000, 1) |
1235 | ps.currentColor = {MathUtil.vector3ArrayLerp(ps.lastColor, ps.targetColor, ps.alpha)} |
1236 | if ps.alpha == 1 then |
1237 | ps.lastColor[1] = ps.currentColor[1] |
1238 | ps.lastColor[2] = ps.currentColor[2] |
1239 | ps.lastColor[3] = ps.currentColor[3] |
1240 | end |
1241 | end |
1242 | |
1243 | 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 |
1244 | ps.alpha = 0 |
1245 | ps.targetColor[1] = ps.wheel.lastColor[1] |
1246 | ps.targetColor[2] = ps.wheel.lastColor[2] |
1247 | ps.targetColor[3] = ps.wheel.lastColor[3] |
1248 | end |
1249 | end |
1250 | |
1251 | if scale > 0 then |
1252 | ParticleUtil.setEmittingState(ps, true) |
1253 | if ps.isTintable then |
1254 | I3DUtil.setShaderParameterRec(ps.shape, "colorAlpha", ps.currentColor[1], ps.currentColor[2], ps.currentColor[3], 1, false) |
1255 | end |
1256 | else |
1257 | ParticleUtil.setEmittingState(ps, false) |
1258 | end |
1259 | |
1260 | -- emit count |
1261 | local maxSpeed = (50 / 3.6) |
1262 | local circum = wheel.radiusOriginal |
1263 | local maxWheelRpm = maxSpeed / circum |
1264 | local wheelRotFactor = Utils.getNoNil(wheel.netInfo.xDriveSpeed, 0) / maxWheelRpm |
1265 | local emitScale = scale * wheelRotFactor * sizeScale |
1266 | ParticleUtil.setEmitCountScale(ps, MathUtil.clamp(emitScale, ps.minScale, ps.maxScale)) |
1267 | |
1268 | -- speeds |
1269 | local speedFactor = 1.0 |
1270 | ParticleUtil.setParticleSystemSpeed(ps, ps.particleSpeed * speedFactor) |
1271 | ParticleUtil.setParticleSystemSpeedRandom(ps, ps.particleRandomSpeed * speedFactor) |
1272 | else |
1273 | ParticleUtil.setEmittingState(ps, false) |
1274 | end |
1275 | end |
1276 | end |
1277 | |
1278 | states[psName] = false |
1279 | end |
1280 | end |
1281 | end |
1282 | end |
1283 | end |
1967 | function Wheels:onWheelChockI3DLoaded(i3dNode, failedReason, args) |
1968 | local wheel = args.wheel |
1969 | local filename = args.filename |
1970 | local xmlFile = args.xmlFile |
1971 | local configKey = args.configKey |
1972 | local chockKey = args.chockKey |
1973 | |
1974 | if i3dNode ~= 0 then |
1975 | local _ |
1976 | local chockNode = getChildAt(i3dNode, 0) |
1977 | local posRefNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "posRefNode"), self.i3dMappings) |
1978 | if posRefNode ~= nil then |
1979 | local chock = {} |
1980 | chock.wheel = wheel |
1981 | chock.node = chockNode |
1982 | chock.filename = filename |
1983 | chock.scale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#scale", "1 1 1", true) |
1984 | setScale(chock.node, unpack(chock.scale)) |
1985 | |
1986 | chock.parkingNode = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#parkingNode", nil, self.components, self.i3dMappings) |
1987 | chock.isInverted = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#isInverted", false) |
1988 | chock.isParked = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#isParked", false) |
1989 | _, chock.height, chock.zOffset = localToLocal(posRefNode, chock.node, 0, 0, 0) |
1990 | chock.height = chock.height / chock.scale[2] |
1991 | chock.zOffset = chock.zOffset / chock.scale[3] |
1992 | |
1993 | chock.offset = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#offset", "0 0 0", true) |
1994 | |
1995 | chock.parkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "parkedNode"), self.i3dMappings) |
1996 | chock.linkedNode = I3DUtil.indexToObject(chockNode, getUserAttribute(chockNode, "linkedNode"), self.i3dMappings) |
1997 | |
1998 | local color = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#color", nil, true) |
1999 | if color ~= nil then |
2000 | local _, _, _, defaultMaterial = getShaderParameter(chockNode, "colorMat0") |
2001 | color[4] = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, chockKey.."#material", defaultMaterial) |
2002 | I3DUtil.setShaderParameterRec(chockNode, "colorMat0", color[1], color[2], color[3], color[4]) |
2003 | end |
2004 | |
2005 | chock.isInParkingPosition = false |
2006 | |
2007 | self:updateWheelChockPosition(chock, chock.isParked) |
2008 | |
2009 | wheel.updateWheelChock = false |
2010 | |
2011 | table.insert(wheel.wheelChocks, chock) |
2012 | table.insert(self.spec_wheels.wheelChocks, chock) |
2013 | else |
2014 | Logging.xmlWarning(xmlFile, "Missing 'posRefNode'-userattribute for wheel-chock '%s'!", chockKey) |
2015 | end |
2016 | |
2017 | delete(i3dNode) |
2018 | end |
2019 | end |
2737 | function Wheels:onWheelPartI3DLoaded(i3dNode, failedReason, args) |
2738 | local spec = self.spec_wheels |
2739 | local wheel = args.wheel |
2740 | local parentWheel = args.parentWheel |
2741 | local linkNode = args.linkNode |
2742 | local name = args.name |
2743 | local filename = args.filename |
2744 | local index = args.index |
2745 | local offset = args.offset |
2746 | local widthAndDiam = args.widthAndDiam |
2747 | local scale = args.scale |
2748 | local fileIdentifier = args.fileIdentifier |
2749 | |
2750 | if i3dNode ~= 0 then |
2751 | wheel[fileIdentifier] = filename |
2752 | wheel[name] = I3DUtil.indexToObject(i3dNode, index) |
2753 | if wheel[name] ~= nil then |
2754 | link(linkNode, wheel[name]) |
2755 | delete(i3dNode) |
2756 | |
2757 | if offset ~= 0 then |
2758 | local dir = 1 |
2759 | if not wheel.isLeft then |
2760 | dir = -1 |
2761 | end |
2762 | setTranslation(wheel[name], offset*dir, 0, 0) |
2763 | end |
2764 | if scale ~= nil then |
2765 | setScale(wheel[name], scale[1], scale[2], scale[3]) |
2766 | end |
2767 | if widthAndDiam ~= nil then |
2768 | if getHasShaderParameter(wheel[name], "widthAndDiam") then |
2769 | I3DUtil.setShaderParameterRec(wheel[name], "widthAndDiam", widthAndDiam[1], widthAndDiam[2], 0, 0, false) |
2770 | else |
2771 | -- convert width and diam to scale (mesh is normalized to 1 meter) |
2772 | local scaleX = MathUtil.inchToM(widthAndDiam[1]) |
2773 | local scaleZY = MathUtil.inchToM(widthAndDiam[2]) |
2774 | setScale(wheel[name], scaleX, scaleZY, scaleZY) |
2775 | end |
2776 | end |
2777 | |
2778 | local rimConfigColor = ConfigurationUtil.getColorByConfigId(self, "rimColor", self.configurations["rimColor"]) |
2779 | local rimColor = wheel.color or rimConfigColor or spec.rimColor |
2780 | |
2781 | if name == "wheelTire" then |
2782 | local zRot = 0 |
2783 | if wheel.tireIsInverted or (parentWheel ~= nil and parentWheel.tireIsInverted) then |
2784 | zRot = math.pi |
2785 | end |
2786 | setRotation(wheel.wheelTire, wheel.xRotOffset, 0, zRot) |
2787 | |
2788 | local x, y, z, _ = I3DUtil.getShaderParameterRec(wheel.wheelTire, "morphPosition") |
2789 | I3DUtil.setShaderParameterRec(wheel.wheelTire, "morphPosition", x, y, z, 0, false) |
2790 | I3DUtil.setShaderParameterRec(wheel.wheelTire, "prevMorphPosition", x, y, z, 0, false) |
2791 | elseif name == "wheelOuterRim" or name == "wheelInnerRim" then |
2792 | if rimColor ~= nil then |
2793 | local r, g, b, mat, _ = unpack(rimColor) |
2794 | mat = wheel.material or mat |
2795 | if wheel.wheelOuterRim ~= nil then |
2796 | -- never use material from config color since it is always '1' |
2797 | if mat == nil then |
2798 | _, _, _, mat = I3DUtil.getShaderParameterRec(wheel.wheelOuterRim, "colorMat0") |
2799 | end |
2800 | I3DUtil.setShaderParameterRec(wheel.wheelOuterRim, "colorMat0", r, g, b, mat, false) |
2801 | end |
2802 | if wheel.wheelInnerRim ~= nil then |
2803 | -- never use material from config color since it is always '1' |
2804 | if mat == nil then |
2805 | _, _, _, mat = I3DUtil.getShaderParameterRec(wheel.wheelInnerRim, "colorMat0") |
2806 | end |
2807 | I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, "colorMat0", r, g, b, mat, false) |
2808 | end |
2809 | end |
2810 | |
2811 | if wheel.wheelInnerRim ~= nil then |
2812 | for i=1, 7 do |
2813 | local color = spec.hubsColors[i] |
2814 | if color ~= nil then |
2815 | I3DUtil.setShaderParameterRec(wheel.wheelInnerRim, string.format("colorMat%d", i), color[1], color[2], color[3], color[4], false) |
2816 | end |
2817 | end |
2818 | end |
2819 | elseif name == "wheelAdditional" then |
2820 | local additionalColor = Utils.getNoNil(wheel.additionalColor or (parentWheel ~= nil and parentWheel.additionalColor or nil), rimColor) |
2821 | if wheel.wheelAdditional ~= nil and additionalColor ~= nil then |
2822 | local r,g,b,_ = unpack(additionalColor) |
2823 | local _,_,_,w = I3DUtil.getShaderParameterRec(wheel.wheelAdditional, "colorMat0") |
2824 | w = (wheel.additionalMaterial or (parentWheel ~= nil and parentWheel.additionalMaterial or nil)) or w |
2825 | I3DUtil.setShaderParameterRec(wheel.wheelAdditional, "colorMat0", r, g, b, w, false) |
2826 | end |
2827 | end |
2828 | else |
2829 | Logging.xmlWarning(self.xmlFile, "Failed to load node '%s' for file '%s'", index, filename) |
2830 | end |
2831 | else |
2832 | if not (self.isDeleted or self.isDeleting) then |
2833 | Logging.xmlWarning(self.xmlFile, "Failed to load file '%s' wheel part '%s'", filename, name) |
2834 | end |
2835 | end |
2836 | end |
2082 | function Wheels:onWheelParticleSystemI3DLoaded(i3dNode, failedReason, args) |
2083 | if i3dNode ~= 0 then |
2084 | local xmlFile = args.xmlFile |
2085 | local configKey = args.configKey |
2086 | local wheelKey = args.wheelKey |
2087 | local wheel = args.wheel |
2088 | local wheelData = args.wheelData |
2089 | |
2090 | local emitterShape = getChildAt(i3dNode, 0) |
2091 | link(wheel.node, emitterShape) |
2092 | delete(i3dNode) |
2093 | |
2094 | local particleSystem = ParticleUtil.copyParticleSystem(xmlFile, nil, args.sourceParticleSystem, emitterShape) |
2095 | particleSystem.i3dFilename = args.i3dFilename |
2096 | particleSystem.particleSpeed = ParticleUtil.getParticleSystemSpeed(particleSystem) |
2097 | particleSystem.particleRandomSpeed = ParticleUtil.getParticleSystemSpeedRandom(particleSystem) |
2098 | |
2099 | particleSystem.isTintable = Utils.getNoNil(getUserAttribute(particleSystem.shape, "tintable"), true) |
2100 | particleSystem.offsets = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#psOffset", "0 0 0", true) |
2101 | local wx, wy, wz = worldToLocal(wheel.node, getWorldTranslation(wheel.driveNode)) |
2102 | setTranslation(particleSystem.emitterShape, wx + particleSystem.offsets[1], wy + particleSystem.offsets[2], wz + particleSystem.offsets[3]) |
2103 | setScale(particleSystem.emitterShape, wheelData.width, wheelData.radius*2, wheelData.radius*2) |
2104 | particleSystem.wheel = wheel |
2105 | particleSystem.rootNode = particleSystem.emitterShape |
2106 | particleSystem.minSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#minSpeed", 3)/3600 |
2107 | particleSystem.maxSpeed = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#maxSpeed", 20)/3600 |
2108 | particleSystem.minScale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#minScale", 0.1) |
2109 | particleSystem.maxScale = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#maxScale", 1) |
2110 | particleSystem.direction = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#direction", 0) |
2111 | particleSystem.onlyActiveOnGroundContact = self:getWheelConfigurationValue(xmlFile, wheel.configIndex, configKey, wheelKey..".wheelParticleSystem#onlyActiveOnGroundContact", true) |
2112 | |
2113 | wheelData.driveGroundParticleSystems[args.name] = {particleSystem} |
2114 | end |
2115 | end |
85 | function Wheels.registerFunctions(vehicleType) |
86 | SpecializationUtil.registerFunction(vehicleType, "getSteeringRotTimeByCurvature", Wheels.getSteeringRotTimeByCurvature) |
87 | SpecializationUtil.registerFunction(vehicleType, "getTurningRadiusByRotTime", Wheels.getTurningRadiusByRotTime) |
88 | SpecializationUtil.registerFunction(vehicleType, "getWheelConfigurationValue", Wheels.getWheelConfigurationValue) |
89 | SpecializationUtil.registerFunction(vehicleType, "loadWheelFromXML", Wheels.loadWheelFromXML) |
90 | SpecializationUtil.registerFunction(vehicleType, "loadWheelBaseData", Wheels.loadWheelBaseData) |
91 | SpecializationUtil.registerFunction(vehicleType, "loadWheelDataFromExternalXML", Wheels.loadWheelDataFromExternalXML) |
92 | SpecializationUtil.registerFunction(vehicleType, "loadWheelSharedData", Wheels.loadWheelSharedData) |
93 | SpecializationUtil.registerFunction(vehicleType, "loadWheelVisualData", Wheels.loadWheelVisualData) |
94 | SpecializationUtil.registerFunction(vehicleType, "loadWheelPhysicsData", Wheels.loadWheelPhysicsData) |
95 | SpecializationUtil.registerFunction(vehicleType, "loadWheelSteeringData", Wheels.loadWheelSteeringData) |
96 | SpecializationUtil.registerFunction(vehicleType, "loadAdditionalWheelsFromXML", Wheels.loadAdditionalWheelsFromXML) |
97 | SpecializationUtil.registerFunction(vehicleType, "loadAdditionalWheelConnectorFromXML", Wheels.loadAdditionalWheelConnectorFromXML) |
98 | SpecializationUtil.registerFunction(vehicleType, "loadWheelChocksFromXML", Wheels.loadWheelChocksFromXML) |
99 | SpecializationUtil.registerFunction(vehicleType, "onWheelChockI3DLoaded", Wheels.onWheelChockI3DLoaded) |
100 | SpecializationUtil.registerFunction(vehicleType, "loadWheelParticleSystem", Wheels.loadWheelParticleSystem) |
101 | SpecializationUtil.registerFunction(vehicleType, "onWheelParticleSystemI3DLoaded", Wheels.onWheelParticleSystemI3DLoaded) |
102 | |
103 | SpecializationUtil.registerFunction(vehicleType, "loadHubsFromXML", Wheels.loadHubsFromXML) |
104 | SpecializationUtil.registerFunction(vehicleType, "loadHubFromXML", Wheels.loadHubFromXML) |
105 | SpecializationUtil.registerFunction(vehicleType, "onWheelHubI3DLoaded", Wheels.onWheelHubI3DLoaded) |
106 | |
107 | SpecializationUtil.registerFunction(vehicleType, "loadAckermannSteeringFromXML", Wheels.loadAckermannSteeringFromXML) |
108 | SpecializationUtil.registerFunction(vehicleType, "loadNonPhysicalWheelFromXML", Wheels.loadNonPhysicalWheelFromXML) |
109 | |
110 | SpecializationUtil.registerFunction(vehicleType, "loadWheelsFromXML", Wheels.loadWheelsFromXML) |
111 | |
112 | SpecializationUtil.registerFunction(vehicleType, "finalizeWheel", Wheels.finalizeWheel) |
113 | SpecializationUtil.registerFunction(vehicleType, "onWheelPartI3DLoaded", Wheels.onWheelPartI3DLoaded) |
114 | SpecializationUtil.registerFunction(vehicleType, "onAdditionalWheelConnectorI3DLoaded", Wheels.onAdditionalWheelConnectorI3DLoaded) |
115 | |
116 | SpecializationUtil.registerFunction(vehicleType, "readWheelDataFromStream", Wheels.readWheelDataFromStream) |
117 | SpecializationUtil.registerFunction(vehicleType, "writeWheelDataToStream", Wheels.writeWheelDataToStream) |
118 | SpecializationUtil.registerFunction(vehicleType, "updateWheelContact", Wheels.updateWheelContact) |
119 | SpecializationUtil.registerFunction(vehicleType, "addTireTrackNode", Wheels.addTireTrackNode) |
120 | SpecializationUtil.registerFunction(vehicleType, "updateTireTrackNode", Wheels.updateTireTrackNode) |
121 | SpecializationUtil.registerFunction(vehicleType, "updateWheelDensityMapHeight", Wheels.updateWheelDensityMapHeight) |
122 | SpecializationUtil.registerFunction(vehicleType, "updateWheelDestruction", Wheels.updateWheelDestruction) |
123 | SpecializationUtil.registerFunction(vehicleType, "getIsWheelFoliageDestructionAllowed", Wheels.getIsWheelFoliageDestructionAllowed) |
124 | SpecializationUtil.registerFunction(vehicleType, "updateWheelSink", Wheels.updateWheelSink) |
125 | SpecializationUtil.registerFunction(vehicleType, "updateWheelFriction", Wheels.updateWheelFriction) |
126 | SpecializationUtil.registerFunction(vehicleType, "updateWheelBase", Wheels.updateWheelBase) |
127 | SpecializationUtil.registerFunction(vehicleType, "updateWheelTireFriction", Wheels.updateWheelTireFriction) |
128 | SpecializationUtil.registerFunction(vehicleType, "setWheelPositionDirty", Wheels.setWheelPositionDirty) |
129 | SpecializationUtil.registerFunction(vehicleType, "setWheelTireFrictionDirty", Wheels.setWheelTireFrictionDirty) |
130 | SpecializationUtil.registerFunction(vehicleType, "getDriveGroundParticleSystemsScale", Wheels.getDriveGroundParticleSystemsScale) |
131 | SpecializationUtil.registerFunction(vehicleType, "getIsVersatileYRotActive", Wheels.getIsVersatileYRotActive) |
132 | SpecializationUtil.registerFunction(vehicleType, "getWheelFromWheelIndex", Wheels.getWheelFromWheelIndex) |
133 | SpecializationUtil.registerFunction(vehicleType, "getWheelByWheelNode", Wheels.getWheelByWheelNode) |
134 | SpecializationUtil.registerFunction(vehicleType, "getWheels", Wheels.getWheels) |
135 | SpecializationUtil.registerFunction(vehicleType, "getCurrentSurfaceSound", Wheels.getCurrentSurfaceSound) |
136 | SpecializationUtil.registerFunction(vehicleType, "getAreSurfaceSoundsActive", Wheels.getAreSurfaceSoundsActive) |
137 | SpecializationUtil.registerFunction(vehicleType, "destroyFruitArea", Wheels.destroyFruitArea) |
138 | SpecializationUtil.registerFunction(vehicleType, "destroySnowArea", Wheels.destroySnowArea) |
139 | SpecializationUtil.registerFunction(vehicleType, "brake", Wheels.brake) |
140 | SpecializationUtil.registerFunction(vehicleType, "getBrakeForce", Wheels.getBrakeForce) |
141 | SpecializationUtil.registerFunction(vehicleType, "updateWheelChocksPosition", Wheels.updateWheelChocksPosition) |
142 | SpecializationUtil.registerFunction(vehicleType, "updateWheelChockPosition", Wheels.updateWheelChockPosition) |
143 | SpecializationUtil.registerFunction(vehicleType, "updateWheelDirtAmount", Wheels.updateWheelDirtAmount) |
144 | SpecializationUtil.registerFunction(vehicleType, "getAllowTireTracks", Wheels.getAllowTireTracks) |
145 | SpecializationUtil.registerFunction(vehicleType, "getTireTrackColor", Wheels.getTireTrackColor) |
146 | SpecializationUtil.registerFunction(vehicleType, "forceUpdateWheelPhysics", Wheels.forceUpdateWheelPhysics) |
147 | SpecializationUtil.registerFunction(vehicleType, "onWheelSnowHeightChanged", Wheels.onWheelSnowHeightChanged) |
148 | end |
384 | function Wheels.registerWheelPhysicsDataXMLPaths(schema, key) |
385 | schema:register(XMLValueType.NODE_INDEX, key .. ".physics#driveNode", "Drive node") |
386 | schema:register(XMLValueType.NODE_INDEX, key .. ".physics#linkNode", "Link node") |
387 | schema:register(XMLValueType.FLOAT, key .. ".physics#yOffset", "Y offset", 0) |
388 | schema:register(XMLValueType.BOOL, key .. ".physics#showSteeringAngle", "Show steering angle", true) |
389 | schema:register(XMLValueType.FLOAT, key .. ".physics#suspTravel", "Suspension travel", 0.01) |
390 | schema:register(XMLValueType.FLOAT, key .. ".physics#initialCompression", "Initial compression value") |
391 | schema:register(XMLValueType.FLOAT, key .. ".physics#deltaY", "Delta Y", 0) |
392 | schema:register(XMLValueType.FLOAT, key .. ".physics#spring", "Spring", 0) |
393 | schema:register(XMLValueType.FLOAT, key .. ".physics#brakeFactor", "Brake factor", 1) |
394 | schema:register(XMLValueType.FLOAT, key .. ".physics#autoHoldBrakeFactor", "Auto hold brake factor", "brakeFactor") |
395 | |
396 | schema:register(XMLValueType.FLOAT, key .. ".physics#damper", "Damper", 0) |
397 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperCompressionLowSpeed", "Damper compression on low speeds") |
398 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperCompressionHighSpeed", "Damper compression on high speeds") |
399 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperCompressionLowSpeedThreshold", "Damper compression on low speeds threshold", 0.1016) |
400 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperRelaxationLowSpeed", "Damper relaxation on low speeds") |
401 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperRelaxationHighSpeed", "Damper relaxation on high speeds") |
402 | schema:register(XMLValueType.FLOAT, key .. ".physics#damperRelaxationLowSpeedThreshold", "Damper relaxation on low speeds threshold", 0.1524) |
403 | |
404 | schema:register(XMLValueType.FLOAT, key .. ".physics#forcePointRatio", "Force point ratio", 0) |
405 | schema:register(XMLValueType.INT, key .. ".physics#driveMode", "Drive mode", 0) |
406 | schema:register(XMLValueType.FLOAT, key .. ".physics#xOffset", "X axis offset", 0) |
407 | schema:register(XMLValueType.FLOAT, key .. ".physics#transRatio", "Suspension translation ratio between repr and drive node", 0) |
408 | |
409 | schema:register(XMLValueType.BOOL, key .. ".physics#isSynchronized", "Wheel is synchronized in multiplayer", true) |
410 | schema:register(XMLValueType.INT, key .. ".physics#tipOcclusionAreaGroupId", "Tip occlusion area group id") |
411 | |
412 | schema:register(XMLValueType.BOOL, key .. ".physics#useReprDirection", "Use repr direction instead of component direction", false) |
413 | schema:register(XMLValueType.BOOL, key .. ".physics#useDriveNodeDirection", "Use drive node direction instead of component direction", false) |
414 | |
415 | schema:register(XMLValueType.FLOAT, key .. ".physics#mass", "Wheel mass (to.)") |
416 | schema:register(XMLValueType.FLOAT, key .. ".physics#radius", "Wheel radius", 0.5) |
417 | schema:register(XMLValueType.FLOAT, key .. ".physics#width", "Wheel width", 0.6) |
418 | |
419 | schema:register(XMLValueType.FLOAT, key .. ".physics#widthOffset", "Wheel width offset", 0) |
420 | schema:register(XMLValueType.FLOAT, key .. ".physics#restLoad", "Wheel load while resting", 1.0) |
421 | schema:register(XMLValueType.FLOAT, key .. ".physics#maxLongStiffness", "Max. longitude stiffness") |
422 | schema:register(XMLValueType.FLOAT, key .. ".physics#maxLatStiffness", "Max. latitude stiffness") |
423 | schema:register(XMLValueType.FLOAT, key .. ".physics#maxLatStiffnessLoad", "Max. latitude stiffness load") |
424 | schema:register(XMLValueType.FLOAT, key .. ".physics#frictionScale", "Wheel friction scale", 1.0) |
425 | schema:register(XMLValueType.FLOAT, key .. ".physics#rotationDamping", "Rotation damping ", "mass * 0.035") |
426 | schema:register(XMLValueType.STRING, key .. ".physics#tireType", "Tire type (mud, offRoad, street, crawler)") |
427 | |
428 | schema:register(XMLValueType.FLOAT, key .. ".physics#fieldDirtMultiplier", "Field dirt multiplier", 75) |
429 | schema:register(XMLValueType.FLOAT, key .. ".physics#streetDirtMultiplier", "Street dirt multiplier", -150) |
430 | schema:register(XMLValueType.FLOAT, key .. ".physics#minDirtPercentage", "Min. dirt scale while cleaning on street drive", 0.35) |
431 | schema:register(XMLValueType.FLOAT, key .. ".physics#maxDirtOffset", "Max. dirt amount offset to global dirt node", 0.5) |
432 | schema:register(XMLValueType.FLOAT, key .. ".physics#dirtColorChangeSpeed", "Defines speed to change the dirt color (sec)", 20) |
433 | |
434 | schema:register(XMLValueType.FLOAT, key .. ".physics#smoothGroundRadius", "Smooth ground radius", "width * 0.75") |
435 | |
436 | schema:register(XMLValueType.BOOL, key .. ".physics#versatileYRot", "Do versatile Y rotation", false) |
437 | schema:register(XMLValueType.BOOL, key .. ".physics#forceVersatility", "Force versatility, also if no ground contact", false) |
438 | schema:register(XMLValueType.BOOL, key .. ".physics#supportsWheelSink", "Supports wheel sink in field", true) |
439 | schema:register(XMLValueType.FLOAT, key .. ".physics#maxWheelSink", "Max. wheel sink in fields", 0.5) |
440 | |
441 | schema:register(XMLValueType.ANGLE, key .. ".physics#rotSpeed", "Rotation speed") |
442 | schema:register(XMLValueType.ANGLE, key .. ".physics#rotSpeedNeg", "Rotation speed in negative direction") |
443 | schema:register(XMLValueType.ANGLE, key .. ".physics#rotMax", "Max. rotation") |
444 | schema:register(XMLValueType.ANGLE, key .. ".physics#rotMin", "Min. rotation") |
445 | |
446 | schema:register(XMLValueType.BOOL, key .. ".physics#invertRotLimit", "Invert the rotation limits") |
447 | schema:register(XMLValueType.FLOAT, key .. ".physics#rotSpeedLimit", "Rotation speed limit") |
448 | end |
350 | function Wheels.registerWheelVisualDataXMLPaths(schema, key) |
351 | schema:register(XMLValueType.STRING, key .. ".tire#filename", "Path to tire i3d file") |
352 | schema:register(XMLValueType.BOOL, key .. ".tire#isInverted", "Tire profile is inverted") |
353 | schema:register(XMLValueType.STRING, key .. ".tire#node", "Node Index inside tire i3d") |
354 | schema:register(XMLValueType.STRING, key .. ".tire#nodeLeft", "Left node index inside tire i3d") |
355 | schema:register(XMLValueType.STRING, key .. ".tire#nodeRight", "Right node index inside tire i3d") |
356 | |
357 | schema:register(XMLValueType.STRING, key .. ".outerRim#filename", "Path to outer rim i3d file") |
358 | schema:register(XMLValueType.STRING, key .. ".outerRim#node", "Outer rim node index in i3d file", "0|0") |
359 | schema:register(XMLValueType.STRING, key .. ".outerRim#nodeLeft", "Outer rim node left index in i3d file", "0|0") |
360 | schema:register(XMLValueType.STRING, key .. ".outerRim#nodeRight", "Outer rim node right index in i3d file", "0|0") |
361 | schema:register(XMLValueType.VECTOR_2, key .. ".outerRim#widthAndDiam", "Width and diameter") |
362 | schema:register(XMLValueType.VECTOR_SCALE, key .. ".outerRim#scale", "Outer rim scale") |
363 | |
364 | schema:register(XMLValueType.STRING, key .. ".innerRim#filename", "Path to inner rim i3d file") |
365 | schema:register(XMLValueType.STRING, key .. ".innerRim#node", "Inner rim node index in i3d file", "0|0") |
366 | schema:register(XMLValueType.STRING, key .. ".innerRim#nodeLeft", "Inner rim node left index in i3d file") |
367 | schema:register(XMLValueType.STRING, key .. ".innerRim#nodeRight", "Inner rim node right index in i3d file") |
368 | schema:register(XMLValueType.VECTOR_2, key .. ".innerRim#widthAndDiam", "Width and diameter") |
369 | schema:register(XMLValueType.FLOAT, key .. ".innerRim#offset", "Inner rim offset", 0) |
370 | schema:register(XMLValueType.VECTOR_SCALE, key .. ".innerRim#scale", "Inner rim scale") |
371 | |
372 | schema:register(XMLValueType.STRING, key .. ".additional#filename", "Path to additional i3d") |
373 | schema:register(XMLValueType.STRING, key .. ".additional#node", "Additional node index in i3d file") |
374 | schema:register(XMLValueType.STRING, key .. ".additional#nodeLeft", "Additional node left index in i3d file") |
375 | schema:register(XMLValueType.STRING, key .. ".additional#nodeRight", "Additional node right index in i3d file") |
376 | schema:register(XMLValueType.FLOAT, key .. ".additional#offset", "Additional node offset", 0) |
377 | schema:register(XMLValueType.VECTOR_SCALE, key .. ".additional#scale", "Additional node scale") |
378 | schema:register(XMLValueType.FLOAT, key .. ".additional#mass", "Additional mass (to.)") |
379 | schema:register(XMLValueType.VECTOR_2, key .. ".additional#widthAndDiam", "Width and diameter") |
380 | end |
3738 | function Wheels:updateWheelBase(wheel) |
3739 | if self.isServer and self.isAddedToPhysics then |
3740 | local positionX, positionY, positionZ = wheel.positionX-wheel.directionX*wheel.deltaY, wheel.positionY-wheel.directionY*wheel.deltaY, wheel.positionZ-wheel.directionZ*wheel.deltaY |
3741 | |
3742 | --#debug if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
3743 | --#debug local x1, y1, z1 = localToWorld(wheel.node, wheel.positionX, wheel.positionY, wheel.positionZ) |
3744 | --#debug local x2, y2, z2 = localToWorld(wheel.node, positionX, positionY, positionZ) |
3745 | --#debug drawDebugLine(x1, y1, z1, 1, 0, 0, x2, y2, z2, 0, 1, 0, false) |
3746 | --#debug end |
3747 | |
3748 | local collisionMask = 255 - 4 -- all up to bit 8, except bit 2 which is set by the players kinematic object |
3749 | wheel.wheelShape = createWheelShape(wheel.node, positionX, positionY, positionZ, wheel.radius, wheel.suspTravel, wheel.spring, wheel.damperCompressionLowSpeed, wheel.damperCompressionHighSpeed, wheel.damperCompressionLowSpeedThreshold, wheel.damperRelaxationLowSpeed, wheel.damperRelaxationHighSpeed, wheel.damperRelaxationLowSpeedThreshold, wheel.mass, collisionMask, wheel.wheelShape) |
3750 | |
3751 | local forcePointY = positionY - wheel.radius * wheel.forcePointRatio |
3752 | local steeringX, steeringY, steeringZ = localToLocal(getParent(wheel.repr), wheel.node, wheel.startPositionX, wheel.startPositionY+wheel.deltaY, wheel.startPositionZ) |
3753 | setWheelShapeForcePoint(wheel.node, wheel.wheelShape, wheel.positionX, forcePointY, positionZ) |
3754 | setWheelShapeSteeringCenter(wheel.node, wheel.wheelShape, steeringX, steeringY, steeringZ) |
3755 | setWheelShapeDirection(wheel.node, wheel.wheelShape, wheel.directionX, wheel.directionY, wheel.directionZ, wheel.axleX, wheel.axleY, wheel.axleZ) |
3756 | setWheelShapeWidth(wheel.node, wheel.wheelShape, wheel.wheelShapeWidth, wheel.widthOffset) |
3757 | |
3758 | if wheel.driveGroundParticleSystems ~= nil then |
3759 | for _,typedPs in pairs(wheel.driveGroundParticleSystems) do |
3760 | for _, ps in ipairs(typedPs) do |
3761 | setTranslation(ps.emitterShape, wheel.positionX + ps.offsets[1], positionY + ps.offsets[2], wheel.positionZ + ps.offsets[3]) |
3762 | end |
3763 | end |
3764 | end |
3765 | end |
3766 | end |
3913 | function Wheels:updateWheelChockPosition(wheelChock, isInParkingPosition) |
3914 | if isInParkingPosition == nil then |
3915 | isInParkingPosition = wheelChock.isInParkingPosition |
3916 | end |
3917 | |
3918 | wheelChock.isInParkingPosition = isInParkingPosition |
3919 | |
3920 | if isInParkingPosition then |
3921 | if wheelChock.parkingNode ~= nil then |
3922 | setTranslation(wheelChock.node, 0, 0, 0) |
3923 | setRotation(wheelChock.node, 0, 0, 0) |
3924 | link(wheelChock.parkingNode, wheelChock.node) |
3925 | setVisibility(wheelChock.node, true) |
3926 | else |
3927 | setVisibility(wheelChock.node, false) |
3928 | end |
3929 | else |
3930 | setVisibility(wheelChock.node, true) |
3931 | local wheel = wheelChock.wheel |
3932 | |
3933 | local radiusChockHeightOffset = wheel.radius - wheel.deformation - wheelChock.height |
3934 | local angle = math.acos(radiusChockHeightOffset / wheel.radius) |
3935 | local zWheelIntersection = wheel.radius * math.sin(angle) |
3936 | local zChockOffset = -zWheelIntersection - wheelChock.zOffset |
3937 | |
3938 | link(wheel.node, wheelChock.node) |
3939 | |
3940 | local _, yRot, _ = localRotationToLocal(getParent(wheel.repr), wheel.node, getRotation(wheel.repr)) |
3941 | if wheelChock.isInverted then |
3942 | yRot = yRot + math.pi |
3943 | end |
3944 | setRotation(wheelChock.node, 0, yRot, 0) |
3945 | |
3946 | local dirX, dirY, dirZ = localDirectionToLocal(wheelChock.node, wheel.node, 0, 0, 1) |
3947 | local normX, normY, normZ = localDirectionToLocal(wheelChock.node, wheel.node, 1, 0, 0) |
3948 | |
3949 | local posX, posY, posZ = localToLocal(wheel.driveNode, wheel.node, 0, 0, 0) |
3950 | posX = posX + normX * wheelChock.offset[1] + dirX * (zChockOffset + wheelChock.offset[3]) |
3951 | posY = posY + normY * wheelChock.offset[1] + dirY * (zChockOffset + wheelChock.offset[3]) - wheel.radius + wheel.deformation + wheelChock.offset[2] |
3952 | posZ = posZ + normZ * wheelChock.offset[1] + dirZ * (zChockOffset + wheelChock.offset[3]) |
3953 | |
3954 | setTranslation(wheelChock.node, posX, posY, posZ) |
3955 | end |
3956 | |
3957 | if wheelChock.parkedNode ~= nil then |
3958 | setVisibility(wheelChock.parkedNode, isInParkingPosition) |
3959 | end |
3960 | |
3961 | if wheelChock.linkedNode ~= nil then |
3962 | setVisibility(wheelChock.linkedNode, not isInParkingPosition) |
3963 | end |
3964 | |
3965 | return true |
3966 | end |
3460 | function Wheels:updateWheelDensityMapHeight(wheel, dt) |
3461 | if not self.isServer then |
3462 | return |
3463 | end |
3464 | |
3465 | local spec = self.spec_wheels |
3466 | |
3467 | -- smoothing of tipAny |
3468 | local wheelSmoothAmount = 0 |
3469 | --if self.lastSpeedReal > 0.0002 and next(spec.wheels) ~= nil then -- start smoothing if driving faster than 0.7km/h |
3470 | if self.lastSpeedReal > 0.0002 then -- start smoothing if driving faster than 0.7km/h |
3471 | wheelSmoothAmount = spec.wheelSmoothAccumulation + math.max(self.lastMovedDistance * 1.2, 0.0003*dt) -- smooth 1.2m per meter driving or at least 0.3m/s |
3472 | local rounded = DensityMapHeightUtil.getRoundedHeightValue(wheelSmoothAmount) |
3473 | spec.wheelSmoothAccumulation = wheelSmoothAmount - rounded |
3474 | else |
3475 | spec.wheelSmoothAccumulation = 0 |
3476 | end |
3477 | |
3478 | if wheelSmoothAmount == 0 then |
3479 | return |
3480 | end |
3481 | |
3482 | -- using netinfo because of tire deformation |
3483 | local wx, wy, wz = wheel.netInfo.x, wheel.netInfo.y, wheel.netInfo.z |
3484 | wy = wy - wheel.radius |
3485 | wx = wx + wheel.xOffset |
3486 | wx, wy, wz = localToWorld(wheel.node, wx,wy,wz) |
3487 | |
3488 | if wheel.smoothGroundRadius > 0 then --and wheelSmoothAmount > 0 then |
3489 | local smoothYOffset = -0.1 |
3490 | local heightType = DensityMapHeightUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, wheel.smoothGroundRadius) |
3491 | if heightType ~= nil and heightType.allowsSmoothing then |
3492 | local terrainHeightUpdater = g_densityMapHeightManager:getTerrainDetailHeightUpdater() |
3493 | if terrainHeightUpdater ~= nil then |
3494 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz) |
3495 | local physicsDeltaHeight = wy - terrainHeight |
3496 | local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale |
3497 | deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset) |
3498 | deltaHeight = math.max(deltaHeight + smoothYOffset, 0) |
3499 | local internalHeight = terrainHeight + deltaHeight |
3500 | smoothDensityMapHeightAtWorldPos(terrainHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, wheel.smoothGroundRadius, wheel.smoothGroundRadius + 1.2) |
3501 | if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
3502 | DebugUtil.drawDebugCircle(wx,internalHeight,wz, wheel.smoothGroundRadius, 10) |
3503 | end |
3504 | end |
3505 | end |
3506 | if wheel.additionalWheels ~= nil then |
3507 | for _, additionalWheel in pairs(wheel.additionalWheels) do |
3508 | local refNode = wheel.repr |
3509 | local xShift,yShift,zShift = localToLocal(additionalWheel.wheelTire, refNode, additionalWheel.xOffset,0,0) |
3510 | wx,wy,wz = localToWorld(refNode, xShift, yShift-additionalWheel.radius, zShift) |
3511 | heightType = DensityMapHeightUtil.getHeightTypeDescAtWorldPos(wx, wy, wz, additionalWheel.smoothGroundRadius) |
3512 | if heightType ~= nil and heightType.allowsSmoothing then |
3513 | local terrainHeightUpdater = g_densityMapHeightManager:getTerrainDetailHeightUpdater() |
3514 | if terrainHeightUpdater ~= nil then |
3515 | local terrainHeight = getTerrainHeightAtWorldPos(g_currentMission.terrainRootNode, wx, wy, wz) |
3516 | local physicsDeltaHeight = wy - terrainHeight |
3517 | local deltaHeight = (physicsDeltaHeight + heightType.collisionBaseOffset) / heightType.collisionScale |
3518 | deltaHeight = math.min(math.max(deltaHeight, physicsDeltaHeight+heightType.minCollisionOffset), physicsDeltaHeight+heightType.maxCollisionOffset) |
3519 | deltaHeight = math.max(deltaHeight + smoothYOffset, 0) |
3520 | local internalHeight = terrainHeight + deltaHeight |
3521 | smoothDensityMapHeightAtWorldPos(terrainHeightUpdater, wx, internalHeight, wz, wheelSmoothAmount, heightType.index, 0.0, additionalWheel.smoothGroundRadius, additionalWheel.smoothGroundRadius + 1.2) |
3522 | if VehicleDebug.state == VehicleDebug.DEBUG_ATTRIBUTES then |
3523 | DebugUtil.drawDebugCircle(wx,internalHeight,wz, additionalWheel.smoothGroundRadius, 10) |
3524 | end |
3525 | end |
3526 | end |
3527 | end |
3528 | end |
3529 | end |
3530 | |
3531 | end |
3535 | function Wheels:updateWheelDestruction(wheel, dt) |
3536 | local doFruitDestruction = self:getIsWheelFoliageDestructionAllowed(wheel) |
3537 | local doSnowDestruction = wheel.contact ~= Wheels.WHEEL_NO_CONTACT and wheel.hasSnowContact |
3538 | if doFruitDestruction or doSnowDestruction then |
3539 | if doFruitDestruction then |
3540 | local x0, _, z0 = getWorldTranslation(wheel.destructionStartNode) |
3541 | if g_currentMission.accessHandler:canFarmAccessLand(self:getActiveFarm(), x0, z0) then |
3542 | local x1, _, z1 = getWorldTranslation(wheel.destructionWidthNode) |
3543 | local x2, _, z2 = getWorldTranslation(wheel.destructionHeightNode) |
3544 | |
3545 | self:destroyFruitArea(x0, z0, x1, z1, x2, z2) |
3546 | end |
3547 | end |
3548 | |
3549 | if doSnowDestruction then |
3550 | -- remove the snow in front of the wheel to avoid drop of the wheel since the collision below changes |
3551 | local snowOffset = wheel.radius * 0.75 * self.movingDirection |
3552 | local x3, _, z3 = localToWorld(wheel.destructionStartNode, 0, 0, snowOffset) |
3553 | local x4, _, z4 = localToWorld(wheel.destructionWidthNode, 0, 0, snowOffset) |
3554 | local x5, _, z5 = localToWorld(wheel.destructionHeightNode, 0, 0, snowOffset) |
3555 | self:destroySnowArea(x3, z3, x4, z4, x5, z5) |
3556 | end |
3557 | |
3558 | if wheel.additionalWheels ~= nil then |
3559 | for _,additionalWheel in pairs(wheel.additionalWheels) do |
3560 | local width = 0.5 * additionalWheel.width |
3561 | local length = math.min(0.5, 0.5 * additionalWheel.width) |
3562 | local refNode = wheel.node |
3563 | |
3564 | if wheel.repr ~= wheel.driveNode then |
3565 | refNode = wheel.repr |
3566 | end |
3567 | |
3568 | local xShift, yShift, zShift = localToLocal(additionalWheel.wheelTire, refNode, 0, 0, 0) |
3569 | |
3570 | if doFruitDestruction then |
3571 | local x0, _, z0 = localToWorld(refNode, xShift + width, yShift, zShift - length) |
3572 | if g_farmlandManager:getIsOwnedByFarmAtWorldPosition(self:getActiveFarm(), x0, z0) then |
3573 | local x1, _, z1 = localToWorld(refNode, xShift - width, yShift, zShift - length) |
3574 | local x2, _, z2 = localToWorld(refNode, xShift + width, yShift, zShift + length) |
3575 | |
3576 | self:destroyFruitArea(x0, z0, x1, z1, x2, z2) |
3577 | end |
3578 | end |
3579 | |
3580 | if doSnowDestruction then |
3581 | local snowOffset = wheel.radius * 0.75 * self.movingDirection |
3582 | local x3, _, z3 = localToWorld(refNode, xShift + width, yShift, zShift - length + snowOffset) |
3583 | local x4, _, z4 = localToWorld(refNode, xShift - width, yShift, zShift - length + snowOffset) |
3584 | local x5, _, z5 = localToWorld(refNode, xShift + width, yShift, zShift + length + snowOffset) |
3585 | |
3586 | self:destroySnowArea(x3, z3, x4, z4, x5, z5) |
3587 | end |
3588 | end |
3589 | end |
3590 | end |
3591 | end |
3132 | function Wheels:updateWheelDirtAmount(nodeData, dt, allowsWashingByRain, rainScale, timeSinceLastRain, temperature) |
3133 | local dirtAmount = self:updateDirtAmount(nodeData, dt, allowsWashingByRain, rainScale, timeSinceLastRain, temperature) |
3134 | |
3135 | local allowManipulation = true |
3136 | if nodeData.wheel ~= nil then |
3137 | if nodeData.wheel.contact == Wheels.WHEEL_NO_CONTACT and nodeData.wheel.forceWheelDirtUpdate ~= true then |
3138 | allowManipulation = false |
3139 | end |
3140 | end |
3141 | |
3142 | if allowManipulation then |
3143 | local spec = self.spec_wheels |
3144 | |
3145 | local isOnField = nodeData.wheel.hasSnowContact |
3146 | if nodeData.wheel ~= nil then |
3147 | if nodeData.wheel.densityType ~= 0 and nodeData.wheel.densityType ~= spec.tireTrackGroundGrassValue and nodeData.wheel.densityType ~= spec.tireTrackGroundGrassCutValue then |
3148 | isOnField = true |
3149 | end |
3150 | end |
3151 | |
3152 | local lastSpeed = self.lastSpeed * 3600 |
3153 | |
3154 | if isOnField then |
3155 | dirtAmount = dirtAmount * nodeData.fieldDirtMultiplier |
3156 | else |
3157 | if nodeData.dirtAmount > nodeData.minDirtPercentage then |
3158 | local speedFactor = lastSpeed / 20 |
3159 | dirtAmount = dirtAmount * nodeData.streetDirtMultiplier * speedFactor |
3160 | end |
3161 | end |
3162 | |
3163 | local globalValue = self.spec_washable.washableNodes[1].dirtAmount |
3164 | local minDirtOffset = nodeData.maxDirtOffset * (math.pow(1-globalValue, 2) * 0.75 + 0.25) |
3165 | local maxDirtOffset = nodeData.maxDirtOffset * (math.pow(1-globalValue, 2) * 0.95 + 0.05) |
3166 | if globalValue - nodeData.dirtAmount > minDirtOffset then |
3167 | if dirtAmount < 0 then |
3168 | dirtAmount = 0 |
3169 | end |
3170 | elseif globalValue - nodeData.dirtAmount < -maxDirtOffset then |
3171 | if dirtAmount > 0 then |
3172 | dirtAmount = 0 |
3173 | end |
3174 | end |
3175 | |
3176 | -- change dirt scale of wheels slowly to snow color when having snow contact |
3177 | -- changing back to normal color takes longer |
3178 | local factor = (nodeData.wheel.hasSnowContact and (temperature or 0) < 1) and 1 or -0.25 |
3179 | local speedFactor = math.min(lastSpeed / 5, 2) |
3180 | local lastSnowScale = nodeData.wheel.snowScale |
3181 | nodeData.wheel.snowScale = math.min(math.max(lastSnowScale + factor * dt * nodeData.dirtColorChangeSpeed * speedFactor, 0), 1) |
3182 | |
3183 | if nodeData.wheel.snowScale ~= nodeData.wheel.lastSnowScale then |
3184 | local defaultColor, snowColor = g_currentMission.environment:getDirtColors() |
3185 | local r, g, b = MathUtil.vector3ArrayLerp(defaultColor, snowColor, nodeData.wheel.snowScale) |
3186 | self:setNodeDirtColor(nodeData, r, g, b) |
3187 | |
3188 | nodeData.wheel.lastSnowScale = nodeData.wheel.snowScale |
3189 | end |
3190 | |
3191 | nodeData.wheel.forceWheelDirtUpdate = false |
3192 | end |
3193 | |
3194 | return dirtAmount |
3195 | end |
3623 | function Wheels:updateWheelSink(wheel, dt, groundWetness) |
3624 | if wheel.supportsWheelSink then |
3625 | if self.isServer and self.isAddedToPhysics then |
3626 | local spec = self.spec_wheels |
3627 | |
3628 | local maxSink = wheel.maxWheelSink |
3629 | local sinkTarget = wheel.sinkTarget |
3630 | local lastSpeed = self:getLastSpeed() |
3631 | local interpolationFactor = 1 |
3632 | |
3633 | if wheel.contact ~= Wheels.WHEEL_NO_CONTACT and lastSpeed > 0.3 then |
3634 | local x, _, z = getWorldTranslation(wheel.repr) |
3635 | |
3636 | local noiseValue = 0 |
3637 | if wheel.densityType > 0 then |
3638 | -- Round to 1cm to avoid sliding when not moving |
3639 | local xPerlin = math.floor(x*100)*0.01 |
3640 | local zPerlin = math.floor(z*100)*0.01 |
3641 | |
3642 | local perlinNoise = Wheels.perlinNoiseSink |
3643 | local noiseSink = 0.5 * (1 + getPerlinNoise2D(xPerlin*perlinNoise.randomFrequency, zPerlin*perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed)) |
3644 | |
3645 | perlinNoise = Wheels.perlinNoiseWobble |
3646 | local noiseWobble = 0.5 * (1 + getPerlinNoise2D(xPerlin*perlinNoise.randomFrequency, zPerlin*perlinNoise.randomFrequency, perlinNoise.persistence, perlinNoise.numOctaves, perlinNoise.randomSeed)) |
3647 | |
3648 | -- estimiate pressure on surface |
3649 | local gravity = 9.81 |
3650 | local tireLoad = getWheelShapeContactForce(wheel.node, wheel.wheelShape) |
3651 | if tireLoad ~= nil then |
3652 | local nx,ny,nz = getWheelShapeContactNormal(wheel.node, wheel.wheelShape) |
3653 | local dx,dy,dz = localDirectionToWorld(wheel.node, 0,-1,0) |
3654 | tireLoad = -tireLoad*MathUtil.dotProduct(dx,dy,dz, nx,ny,nz) |
3655 | |
3656 | tireLoad = tireLoad + math.max(ny*gravity, 0.0) * wheel.mass -- add gravity force of tire |
3657 | else |
3658 | tireLoad = 0 |
3659 | end |
3660 | tireLoad = tireLoad / gravity |
3661 | |
3662 | local loadFactor = math.min(1.0, math.max(0, tireLoad / wheel.maxLatStiffnessLoad)) |
3663 | |
3664 | noiseSink = 0.333*(2*loadFactor + groundWetness) * noiseSink |
3665 | |
3666 | noiseValue = math.max(noiseSink, noiseWobble) |
3667 | end |
3668 | |
3669 | maxSink = Wheels.MAX_SINK[wheel.densityType] or maxSink |
3670 | |
3671 | -- plowing effect |
3672 | if wheel.densityType == FieldGroundType.PLOWED and wheel.oppositeWheelIndex ~= nil then |
3673 | local oppositeWheel = spec.wheels[wheel.oppositeWheelIndex] |
3674 | if oppositeWheel.densityType ~= nil and oppositeWheel.densityType ~= FieldGroundType.PLOWED then |
3675 | maxSink = maxSink * 1.3 |
3676 | end |
3677 | end |
3678 | |
3679 | sinkTarget = math.min(0.2*wheel.radiusOriginal, math.min(maxSink, wheel.maxWheelSink) * noiseValue) |
3680 | elseif wheel.contact == Wheels.WHEEL_NO_CONTACT then |
3681 | sinkTarget = 0 |
3682 | lastSpeed = 10 |
3683 | interpolationFactor = 0.075 -- smoother interpolation back to normal radius in case we directly sink again after having ground contact -> this avoid jittering |
3684 | end |
3685 | |
3686 | if wheel.sinkTarget < sinkTarget then |
3687 | wheel.sinkTarget = math.min(sinkTarget, wheel.sinkTarget + (0.05 * math.min(30, math.max(0, lastSpeed-0.2)) * (dt/1000) * interpolationFactor)) |
3688 | elseif wheel.sinkTarget > sinkTarget then |
3689 | wheel.sinkTarget = math.max(sinkTarget, wheel.sinkTarget - (0.05 * math.min(30, math.max(0, lastSpeed-0.2)) * (dt/1000) * interpolationFactor)) |
3690 | end |
3691 | |
3692 | if math.abs(wheel.sink - wheel.sinkTarget) > 0.001 then |
3693 | wheel.sink = wheel.sinkTarget |
3694 | |
3695 | local radius = wheel.radiusOriginal - wheel.sink |
3696 | if radius ~= wheel.radius then |
3697 | wheel.radius = radius |
3698 | if self.isServer then |
3699 | self:setWheelPositionDirty(wheel) |
3700 | |
3701 | local sinkFactor = (wheel.sink/maxSink) * (1 + (0.4 * groundWetness)) |
3702 | wheel.sinkLongStiffnessFactor = (1.0 - (0.10 * sinkFactor)) |
3703 | wheel.sinkLatStiffnessFactor = (1.0 - (0.20 * sinkFactor)) |
3704 | self:setWheelTireFrictionDirty(wheel) |
3705 | end |
3706 | end |
3707 | end |
3708 | end |
3709 | end |
3710 | end |