228 | function WheelsUtil.getSmoothedAcceleratorAndBrakePedals(self, acceleratorPedal, brakePedal, dt) |
229 | |
230 | if self.wheelsUtilSmoothedAcceleratorPedal == nil then |
231 | self.wheelsUtilSmoothedAcceleratorPedal = 0 |
232 | end |
233 | |
234 | local appliedAcc = 0 |
235 | if acceleratorPedal > 0 then |
236 | if acceleratorPedal > self.wheelsUtilSmoothedAcceleratorPedal then |
237 | appliedAcc = math.min(math.max(self.wheelsUtilSmoothedAcceleratorPedal + SMOOTHING_SPEED_SCALE * dt, SMOOTHING_SPEED_SCALE), acceleratorPedal) |
238 | else |
239 | appliedAcc = acceleratorPedal |
240 | end |
241 | self.wheelsUtilSmoothedAcceleratorPedal = appliedAcc |
242 | elseif acceleratorPedal < 0 then |
243 | if acceleratorPedal < self.wheelsUtilSmoothedAcceleratorPedal then |
244 | appliedAcc = math.max(math.min(self.wheelsUtilSmoothedAcceleratorPedal - SMOOTHING_SPEED_SCALE * dt, -SMOOTHING_SPEED_SCALE), acceleratorPedal) |
245 | else |
246 | appliedAcc = acceleratorPedal |
247 | end |
248 | self.wheelsUtilSmoothedAcceleratorPedal = appliedAcc |
249 | else |
250 | -- Decrease smoothed acceleration towards 0 with different speeds based on if we are braking |
251 | local decSpeed = 0.0005 + 0.001 * brakePedal -- scale between 2sec and 0.66s (full brake) |
252 | if self.wheelsUtilSmoothedAcceleratorPedal > 0 then |
253 | self.wheelsUtilSmoothedAcceleratorPedal = math.max(self.wheelsUtilSmoothedAcceleratorPedal - decSpeed*dt, 0) |
254 | else |
255 | self.wheelsUtilSmoothedAcceleratorPedal = math.min(self.wheelsUtilSmoothedAcceleratorPedal + decSpeed*dt, 0) |
256 | end |
257 | end |
258 | |
259 | if self.wheelsUtilSmoothedBrakePedal == nil then |
260 | self.wheelsUtilSmoothedBrakePedal = 0 |
261 | end |
262 | |
263 | local appliedBrake = 0 |
264 | if brakePedal > 0 then |
265 | if brakePedal > self.wheelsUtilSmoothedBrakePedal then |
266 | appliedBrake = math.min(self.wheelsUtilSmoothedBrakePedal + 0.0025*dt, brakePedal) -- full brake in 0.4sec |
267 | else |
268 | appliedBrake = brakePedal |
269 | end |
270 | self.wheelsUtilSmoothedBrakePedal = appliedBrake |
271 | else |
272 | -- Decrease smoothed brake towards 0 with different speeds based on if we are accelerating |
273 | local decSpeed = 0.0005 + 0.001 * acceleratorPedal -- scale between 2sec and 0.66s (full acceleration) |
274 | self.wheelsUtilSmoothedBrakePedal = math.max(self.wheelsUtilSmoothedBrakePedal - decSpeed*dt, 0) |
275 | end |
276 | |
277 | --print(string.format("input: %.2f %.2f applied: %.2f %.2f", acceleratorPedal, brakePedal, appliedAcc, appliedBrake)) |
278 | |
279 | return appliedAcc, appliedBrake |
280 | end |
610 | function WheelsUtil.updateVisualWheel(self, wheel, x, y, z, xDrive, suspensionLength) |
611 | local changed = false |
612 | |
613 | local steeringAngle = wheel.steeringAngle |
614 | if not wheel.showSteeringAngle then |
615 | steeringAngle = 0 |
616 | end |
617 | |
618 | if math.abs(steeringAngle-wheel.lastSteeringAngle) > STEERING_ANGLE_THRESHOLD then |
619 | setRotation(wheel.repr, 0, steeringAngle, 0) |
620 | wheel.lastSteeringAngle = steeringAngle |
621 | changed = true |
622 | end |
623 | |
624 | if math.abs(xDrive-wheel.lastXDrive) > STEERING_ANGLE_THRESHOLD then |
625 | setRotation(wheel.driveNode, xDrive, 0, 0) |
626 | wheel.lastXDrive = xDrive |
627 | changed = true |
628 | end |
629 | |
630 | if wheel.wheelTire ~= nil then |
631 | if self.spec_wheels.wheelVisualPressureActive then |
632 | local deformation = MathUtil.clamp((wheel.deltaY + wheel.initialDeformation - suspensionLength), 0, wheel.maxDeformation) |
633 | local prevDeformation, curDeformation |
634 | if math.abs(deformation - wheel.deformation) > 0.003 then |
635 | prevDeformation = wheel.deformation |
636 | curDeformation = deformation |
637 | |
638 | wheel.deformation = deformation |
639 | wheel.derformationPrevDirty = true |
640 | |
641 | changed = true |
642 | end |
643 | |
644 | -- one frame delayed we update prev deformation to match cur deformation (TAA) |
645 | if wheel.derformationPrevDirty then |
646 | prevDeformation = wheel.deformation |
647 | curDeformation = wheel.deformation |
648 | |
649 | wheel.derformationPrevDirty = false |
650 | end |
651 | |
652 | if curDeformation ~= nil then |
653 | local mx, my, mz, _ = I3DUtil.getShaderParameterRec(wheel.wheelTire, "morphPosition") |
654 | I3DUtil.setShaderParameterRec(wheel.wheelTire, "morphPosition", mx, my, mz, curDeformation, false) |
655 | I3DUtil.setShaderParameterRec(wheel.wheelTire, "prevMorphPosition", mx, my, mz, prevDeformation, false) |
656 | |
657 | if wheel.additionalWheels ~= nil then |
658 | for _, additionalWheel in pairs(wheel.additionalWheels) do |
659 | mx, my, mz, _ = I3DUtil.getShaderParameterRec(additionalWheel.wheelTire, "morphPosition") |
660 | I3DUtil.setShaderParameterRec(additionalWheel.wheelTire, "morphPosition", mx, my, mz, curDeformation, false) |
661 | I3DUtil.setShaderParameterRec(additionalWheel.wheelTire, "prevMorphPosition", mx, my, mz, prevDeformation, false) |
662 | end |
663 | end |
664 | end |
665 | |
666 | -- increase suspension length a bit to make sure the wheel sides are touching the ground and not only the center |
667 | -- temporary disabled until we find a final solution |
668 | local sideOffset = 0--math.min((1-wheel.sideDeformOffset) * (wheel.radius-deformation), wheel.maxDeformation) |
669 | |
670 | suspensionLength = suspensionLength + deformation + sideOffset |
671 | end |
672 | end |
673 | |
674 | suspensionLength = suspensionLength - wheel.deltaY |
675 | |
676 | if math.abs(wheel.lastMovement-suspensionLength) > SUSPENSION_THRESHOLD then |
677 | local dirX, dirY, dirZ = localDirectionToLocal(wheel.repr, getParent(wheel.repr), 0, -1, 0) |
678 | local transRatio = wheel.transRatio |
679 | local movement = suspensionLength * transRatio |
680 | setTranslation(wheel.repr, wheel.startPositionX + dirX*movement, wheel.startPositionY + dirY*movement, wheel.startPositionZ + dirZ*movement) |
681 | changed = true |
682 | if transRatio < 1 then |
683 | movement = suspensionLength*(1-transRatio) |
684 | setTranslation(wheel.driveNode, wheel.driveNodeStartPosX + dirX*movement, wheel.driveNodeStartPosY + dirY*movement, wheel.driveNodeStartPosZ + dirZ*movement) |
685 | end |
686 | |
687 | wheel.lastMovement = suspensionLength |
688 | end |
689 | |
690 | if wheel.steeringNode ~= nil then |
691 | local refAngle = wheel.steeringNodeMaxRot |
692 | local refTrans = wheel.steeringNodeMaxTransX |
693 | local refRot = wheel.steeringNodeMaxRotY |
694 | if steeringAngle < 0 then |
695 | refAngle = wheel.steeringNodeMinRot |
696 | refTrans = wheel.steeringNodeMinTransX |
697 | refRot = wheel.steeringNodeMinRotY |
698 | end |
699 | local steering = 0 |
700 | if refAngle ~= 0 then |
701 | steering = steeringAngle / refAngle |
702 | end |
703 | |
704 | if wheel.steeringNodeMinTransX ~= nil then |
705 | local x,y,z = getTranslation(wheel.steeringNode) |
706 | x = refTrans * steering |
707 | setTranslation(wheel.steeringNode, x, y, z) |
708 | end |
709 | if wheel.steeringNodeMinRotY ~= nil then |
710 | local rotX,rotY,rotZ = getRotation(wheel.steeringRotNode or wheel.steeringNode) |
711 | rotY = refRot * steering |
712 | setRotation(wheel.steeringRotNode or wheel.steeringNode, rotX, rotY, rotZ) |
713 | end |
714 | end |
715 | |
716 | for i=1, #wheel.fenders do |
717 | local fender = wheel.fenders[i] |
718 | |
719 | local angleDif = 0 |
720 | if steeringAngle > fender.rotMax then |
721 | angleDif = fender.rotMax - steeringAngle |
722 | elseif steeringAngle < fender.rotMin then |
723 | angleDif = fender.rotMin - steeringAngle |
724 | end |
725 | setRotation(fender.node, 0, angleDif, 0) |
726 | end |
727 | |
728 | return changed |
729 | end |
289 | function WheelsUtil.updateWheelsPhysics(self, dt, currentSpeed, acceleration, doHandbrake, stopAndGoBraking) |
290 | --print("function WheelsUtil.updateWheelsPhysics("..tostring(self)..", "..tostring(dt)..", "..tostring(currentSpeed)..", "..tostring(acceleration)..", "..tostring(doHandbrake)..", "..tostring(stopAndGoBraking)) |
291 | |
292 | local acceleratorPedal = 0 |
293 | local brakePedal = 0 |
294 | |
295 | local reverserDirection = 1 |
296 | if self.spec_drivable ~= nil then |
297 | reverserDirection = self.spec_drivable.reverserDirection |
298 | end |
299 | |
300 | local motor = self.spec_motorized.motor |
301 | local isManualTransmission = motor.backwardGears ~= nil or motor.forwardGears ~= nil |
302 | local useManualDirectionChange = (isManualTransmission and motor.gearShiftMode ~= VehicleMotor.SHIFT_MODE_AUTOMATIC) |
303 | or motor.directionChangeMode == VehicleMotor.DIRECTION_CHANGE_MODE_MANUAL |
304 | useManualDirectionChange = useManualDirectionChange and self:getIsManualDirectionChangeAllowed() |
305 | if useManualDirectionChange then |
306 | acceleration = acceleration * motor.currentDirection |
307 | else |
308 | acceleration = acceleration * reverserDirection |
309 | end |
310 | |
311 | local absCurrentSpeed = math.abs(currentSpeed) |
312 | local accSign = MathUtil.sign(acceleration) |
313 | |
314 | self.nextMovingDirection = self.nextMovingDirection or 0 |
315 | self.nextMovingDirectionTimer = self.nextMovingDirectionTimer or 0 |
316 | |
317 | local automaticBrake = false |
318 | if math.abs(acceleration) < 0.001 then |
319 | automaticBrake = true |
320 | |
321 | -- Non-stop&go only allows change of direction if the vehicle speed is smaller than 1km/h or the direction has already changed (e.g. because the brakes are not hard enough) |
322 | if stopAndGoBraking or currentSpeed * self.nextMovingDirection < 0.0003 then |
323 | self.nextMovingDirection = 0 |
324 | end |
325 | else |
326 | -- Disable the known moving direction if the vehicle is driving more than 5km/h (0.0014 * 3600 = 5.04km/h) in the opposite direction |
327 | if self.nextMovingDirection * currentSpeed < -0.0014 then |
328 | self.nextMovingDirection = 0 |
329 | end |
330 | |
331 | -- Continue accelerating if we want to go in the same direction |
332 | -- or if the vehicle is only moving slowly in the wrong direction (0.0003 * 3600 = 1.08 km/h) and we are allowed to change direction |
333 | if accSign == self.nextMovingDirection or (currentSpeed * accSign > -0.0003 and (stopAndGoBraking or self.nextMovingDirection == 0)) then |
334 | self.nextMovingDirectionTimer = math.max(self.nextMovingDirectionTimer - dt, 0) |
335 | if self.nextMovingDirectionTimer == 0 then |
336 | acceleratorPedal = acceleration |
337 | brakePedal = 0 |
338 | self.nextMovingDirection = accSign |
339 | else |
340 | acceleratorPedal = 0 |
341 | brakePedal = math.abs(acceleration) |
342 | end |
343 | else |
344 | acceleratorPedal = 0 |
345 | brakePedal = math.abs(acceleration) |
346 | if stopAndGoBraking then |
347 | self.nextMovingDirectionTimer = 100 |
348 | end |
349 | end |
350 | end |
351 | |
352 | if useManualDirectionChange then |
353 | if acceleratorPedal ~= 0 and MathUtil.sign(acceleratorPedal) ~= motor.currentDirection then |
354 | brakePedal = math.abs(acceleratorPedal) |
355 | acceleratorPedal = 0 |
356 | end |
357 | end |
358 | |
359 | if automaticBrake then |
360 | acceleratorPedal = 0 |
361 | end |
362 | |
363 | acceleratorPedal, brakePedal = motor:updateGear(acceleratorPedal, brakePedal, dt) |
364 | |
365 | if motor.gear == 0 and motor.targetGear ~= 0 then |
366 | -- brake automatically if the vehicle is rolling backwards while shifting |
367 | if currentSpeed * MathUtil.sign(motor.targetGear) < 0 then |
368 | automaticBrake = true |
369 | end |
370 | end |
371 | |
372 | if motor.gearShiftMode == VehicleMotor.SHIFT_MODE_MANUAL_CLUTCH then |
373 | if isManualTransmission then |
374 | automaticBrake = false |
375 | end |
376 | end |
377 | |
378 | if automaticBrake then |
379 | local isSlow = absCurrentSpeed < motor.lowBrakeForceSpeedLimit |
380 | local isArticulatedSteering = self.spec_articulatedAxis ~= nil and self.spec_articulatedAxis.componentJoint ~= nil and math.abs(self.rotatedTime) > 0.01 |
381 | |
382 | if (isSlow or doHandbrake) and not isArticulatedSteering then |
383 | brakePedal = 1 |
384 | else |
385 | -- interpolate between lowBrakeForce and 1 if speed is below 3.6 km/h |
386 | local factor = math.min(absCurrentSpeed / 0.001, 1) |
387 | brakePedal = MathUtil.lerp(1, motor.lowBrakeForceScale, factor) |
388 | end |
389 | end |
390 | |
391 | SpecializationUtil.raiseEvent(self, "onVehiclePhysicsUpdate", acceleratorPedal, brakePedal, automaticBrake, currentSpeed) |
392 | |
393 | acceleratorPedal, brakePedal = WheelsUtil.getSmoothedAcceleratorAndBrakePedals(self, acceleratorPedal, brakePedal, dt) |
394 | |
395 | local maxSpeed = motor:getMaximumForwardSpeed() * 3.6 |
396 | if self.movingDirection < 0 then |
397 | maxSpeed = motor:getMaximumBackwardSpeed() * 3.6 |
398 | end |
399 | |
400 | --active braking if over the speed limit |
401 | local overSpeedLimit = self:getLastSpeed() - math.min(motor:getSpeedLimit(), maxSpeed) |
402 | if overSpeedLimit > 0 then |
403 | if overSpeedLimit > 0.3 then |
404 | motor.overSpeedTimer = math.min(motor.overSpeedTimer + dt, 2000) |
405 | else |
406 | motor.overSpeedTimer = math.max(motor.overSpeedTimer - dt, 0) |
407 | end |
408 | |
409 | -- the longer we exceed the speed limit by min. 0.3km/h, the harder we brake |
410 | -- so we have a smooth braking when the speed limit changes and a harder brake when driving downhill with a full trailer |
411 | local factor = 0.5 + (motor.overSpeedTimer / 2000 * 1) |
412 | |
413 | brakePedal = math.max(math.min(math.pow(overSpeedLimit * factor, 2), 1), brakePedal) |
414 | acceleratorPedal = 0.2 * math.max(1 - overSpeedLimit/0.2, 0) * acceleratorPedal -- fadeout the accelerator pedal over 0.2km/h, but immediately reduce to 20% (don't set to 0 directly so that the physics engine can still compensate if the brakes are too hard) |
415 | else |
416 | acceleratorPedal = acceleratorPedal * math.min(math.abs(overSpeedLimit) / 0.3 + 0.2, 1) |
417 | motor.overSpeedTimer = 0 |
418 | end |
419 | |
420 | if next(self.spec_motorized.differentials) ~= nil and self.spec_motorized.motorizedNode ~= nil then |
421 | |
422 | local absAcceleratorPedal = math.abs(acceleratorPedal) |
423 | local minGearRatio, maxGearRatio = motor:getMinMaxGearRatio() |
424 | |
425 | local maxSpeed |
426 | if maxGearRatio >= 0 then |
427 | maxSpeed = motor:getMaximumForwardSpeed() |
428 | else |
429 | maxSpeed = motor:getMaximumBackwardSpeed() |
430 | end |
431 | |
432 | local acceleratorPedalControlsSpeed = false |
433 | if acceleratorPedalControlsSpeed then |
434 | maxSpeed = maxSpeed * absAcceleratorPedal |
435 | if absAcceleratorPedal > 0.001 then |
436 | absAcceleratorPedal = 1 |
437 | end |
438 | end |
439 | maxSpeed = math.min(maxSpeed, motor:getSpeedLimit() / 3.6) |
440 | local maxAcceleration = motor:getAccelerationLimit() |
441 | local maxMotorRotAcceleration = motor:getMotorRotationAccelerationLimit() |
442 | local minMotorRpm, maxMotorRpm = motor:getRequiredMotorRpmRange() |
443 | |
444 | local neededPtoTorque, ptoTorqueVirtualMultiplicator = PowerConsumer.getTotalConsumedPtoTorque(self) |
445 | neededPtoTorque = neededPtoTorque / motor:getPtoMotorRpmRatio() |
446 | local neutralActive = (minGearRatio == 0 and maxGearRatio == 0) or motor:getManualClutchPedal() > 0.90 |
447 | |
448 | motor:setExternalTorqueVirtualMultiplicator(ptoTorqueVirtualMultiplicator) |
449 | |
450 | --print(string.format("set vehicle props: accPed=%.1f speed=%.1f gearRatio=[%.1f %.1f] rpm=[%.1f %.1f], ptoTorque=[%.1f]", absAcceleratorPedal, maxSpeed, minGearRatio, maxGearRatio, minMotorRpm, maxMotorRpm, neededPtoTorque)) |
451 | if not neutralActive then |
452 | self:controlVehicle(absAcceleratorPedal, maxSpeed, maxAcceleration, minMotorRpm*math.pi/30, maxMotorRpm*math.pi/30, maxMotorRotAcceleration, minGearRatio, maxGearRatio, motor:getMaxClutchTorque(), neededPtoTorque) |
453 | else |
454 | self:controlVehicle(0.0, 0.0, 0.0, 0.0, math.huge, 0.0, 0.0, 0.0, 0.0, 0.0) |
455 | |
456 | -- slightly break while using manual + clutch and in neutral position |
457 | -- to simulate a bit of rolling resistance |
458 | brakePedal = math.max(brakePedal, 0.03) |
459 | end |
460 | end |
461 | |
462 | self:brake(brakePedal) |
463 | end |
485 | function WheelsUtil.updateWheelSteeringAngle(self, wheel, dt) |
486 | |
487 | local steeringAngle = wheel.steeringAngle |
488 | local rotatedTime = self.rotatedTime |
489 | |
490 | if wheel.steeringAxleScale ~= nil and wheel.steeringAxleScale ~= 0 then |
491 | local steeringAxleAngle = 0 |
492 | if self.spec_attachable ~= nil then |
493 | steeringAxleAngle = self.spec_attachable.steeringAxleAngle |
494 | end |
495 | steeringAngle = MathUtil.clamp(steeringAxleAngle * wheel.steeringAxleScale, wheel.steeringAxleRotMin, wheel.steeringAxleRotMax) |
496 | elseif wheel.versatileYRot and self:getIsVersatileYRotActive(wheel) then |
497 | if self.isServer then |
498 | if wheel.forceVersatility or wheel.hasGroundContact then |
499 | steeringAngle = Utils.getVersatileRotation(wheel.repr, wheel.node, dt, wheel.positionX, wheel.positionY, wheel.positionZ, wheel.steeringAngle, wheel.rotMin, wheel.rotMax) |
500 | end |
501 | end |
502 | elseif (wheel.rotSpeed ~= 0 and wheel.rotMax ~= nil and wheel.rotMin ~= nil) or wheel.forceSteeringAngleUpdate then |
503 | if rotatedTime > 0 or wheel.rotSpeedNeg == nil then |
504 | steeringAngle = rotatedTime * wheel.rotSpeed |
505 | else |
506 | steeringAngle = rotatedTime * wheel.rotSpeedNeg |
507 | end |
508 | if steeringAngle > wheel.rotMax then |
509 | steeringAngle = wheel.rotMax |
510 | elseif steeringAngle < wheel.rotMin then |
511 | steeringAngle = wheel.rotMin |
512 | end |
513 | if self.customSteeringAngleFunction then |
514 | steeringAngle = self:updateSteeringAngle(wheel, dt, steeringAngle) |
515 | end |
516 | end |
517 | |
518 | wheel.steeringAngle = steeringAngle |
519 | end |