C
Qt Quick Ultralite Motorcycle Cluster Demo
// Copyright (C) 2024 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial
#include "simulation/drivetrain.h"
#include <assert.h>
#include <algorithm>
#include <cmath>
#include "mathutils.h"
namespace {
const uint32_t ShiftUpDelay = 800;
const uint16_t AccChangeRandomizePeriod = 600;
const float AccMax = 6.5f;
const float RandomAccMax = 4.f;
const int ProportionalAccDivider = 2;
const float SpeedFinishStepPrecision = 2.f;
const float FuelMinDiff = 0.2f;
const float SpeedToDistanceCoef = 1000000.f;
const float cmPerMinuteToKmhRatio = 0.0006;
const int NeutralGearNumber = 0;
float getRandomizeAccChange()
{
return randomize(-RandomAccMax, RandomAccMax);
}
uint32_t slowDownRpmBeforeGearShift(const uint32_t rpm, const uint32_t gearShiftRpmLvl)
{
return (rpm * 3 + gearShiftRpmLvl * 2) / 5;
}
} // namespace
namespace Simulation {
Drivetrain::Drivetrain(const Config &config)
: _config(config)
{
assert(_config.gearNum <= Config::MAX_GEARS);
reset();
}
void Drivetrain::forceNeutraulGear(const bool setNeutral)
{
_data.gear = setNeutral ? NeutralGearNumber : 1;
}
void Drivetrain::setSpeedTarget(const int targetSpeed, const bool withRandomAccChanges)
{
_data.targetSpeed = targetSpeed;
_enableRandomAccChanges = withRandomAccChanges;
}
void Drivetrain::setFuelTarget(const float targetFuel, const float fuelSpeed)
{
_data.targetFuel = targetFuel;
_data.fuelSpeed = fuelSpeed;
}
void Drivetrain::setDistanceToTarget(const float distance)
{
_data.distanceToTarget = distance;
}
void Drivetrain::update(const uint32_t tick)
{
_timeFromLastRandomAccChange += tick;
const float delta = _data.targetSpeed - _data.speed;
float acceleration = clamp(delta, -AccMax, AccMax) / ProportionalAccDivider;
if (_enableRandomAccChanges && _timeFromLastRandomAccChange >= AccChangeRandomizePeriod) {
_timeFromLastRandomAccChange = 0;
acceleration += getRandomizeAccChange();
}
calculateSpeedData(tick, acceleration);
}
void Drivetrain::calculateSpeedData(const uint32_t tick, const float acceleration)
{
if (_data.gear > NeutralGearNumber) {
updateRpm(tick, acceleration);
updateGearShift(tick);
updateSpeed();
updateOdometers(tick);
}
updateFuelLevel();
}
void Drivetrain::updateRpm(const uint32_t tick, const float acceleration)
{
const float currentRatio = (_config.gearRatios[_data.gear]);
_data.acceleration = (acceleration * 3 + _data.acceleration) / 4;
_data.rpm += tick * _data.acceleration * currentRatio;
if (_data.gear != _config.gearNum - 1 && _data.rpm > _config.shiftUpAtRpm) {
_data.rpm = slowDownRpmBeforeGearShift(_data.rpm, _config.shiftUpAtRpm);
}
_data.rpm = clamp(_data.rpm, _config.minRpm, _config.maxRpm);
}
void Drivetrain::updateGearShift(const uint32_t tick)
{
if (_data.rpm >= _config.shiftUpAtRpm && _data.gear < (_config.gearNum - 1)) {
if (_timeCounterForShiftUp >= ShiftUpDelay) {
_timeCounterForShiftUp = 0;
shiftGear(1);
} else {
_timeCounterForShiftUp += tick;
}
} else if (_data.rpm <= _config.shiftDownAtRpm && _data.gear > NeutralGearNumber + 1) {
shiftGear(-1);
} else {
_timeCounterForShiftUp = 0;
}
}
void Drivetrain::shiftGear(const int delta)
{
_data.gear += delta;
assert(_data.gear > NeutralGearNumber && _data.gear < _config.gearNum);
_data.rpm = calculateRpm(_data.speed, _data.gear);
}
int Drivetrain::calculateRpm(const int speed, const int gear) const
{
if (gear < 1 || gear >= _config.gearNum) {
assert(!"Wrong gear!");
return 0;
}
return int((speed * _config.gearRatios[gear] * _config.diffRatio)
/ (_config.tireCircumference * cmPerMinuteToKmhRatio));
}
void Drivetrain::updateSpeed()
{
_data.speed = calculateSpeed(_data.rpm, _data.gear);
}
int Drivetrain::calculateSpeed(const int rpm, const int gear) const
{
if (gear < 1 || gear >= _config.gearNum) {
assert(!"Wrong gear!");
return 0;
}
return int((_config.tireCircumference * rpm) / (_config.gearRatios[gear] * _config.diffRatio)
* cmPerMinuteToKmhRatio);
}
void Drivetrain::updateFuelLevel()
{
float ¤tFuel = _data.fuelLevel;
float &targetFuel = _data.targetFuel;
if (fabs(targetFuel - currentFuel) <= FuelMinDiff) {
currentFuel = targetFuel;
} else {
currentFuel += ((targetFuel - currentFuel) > 0 ? 1 : -1 * _data.speed / 100.f) * _data.fuelSpeed;
}
}
void Drivetrain::updateOdometers(const uint32_t tick)
{
const float newDistance = _data.speed * tick / SpeedToDistanceCoef;
_data.odometer += newDistance;
_data.tripDistance += newDistance;
_data.distanceToTarget -= newDistance;
_data.distanceToTarget = _data.distanceToTarget > 0 ? _data.distanceToTarget : 0;
}
void Drivetrain::reset()
{
_data.speed = 0;
_data.rpm = 0;
_data.gear = NeutralGearNumber + 1;
_data.fuelLevel = 30;
_data.odometer = 5891;
_data.tripDistance = 247.4;
_data.distanceToTarget = 9.1;
_data.acceleration = 0.f;
_data.targetSpeed = 0.f;
}
const Drivetrain::DriveData &Drivetrain::getDriveData() const
{
return _data;
}
bool Drivetrain::isTargetSpeedReached() const
{
return abs(_data.targetSpeed - _data.speed) < SpeedFinishStepPrecision;
}
} // namespace Simulation