AirSim中的物理引擎

“物理引擎”是指在一款仿真软件/平台中使用的模拟现实世界物理逻辑(如碰撞、各种有形变/无形变的力学等)的计算单元,例如在开源机器人仿真平台gazebo中,就支持如ODE、Bullets、SimBody等多种物理引擎。

在AirSim中,针对无人机MultiRotor的物理引擎是AirSim自己开发的FastPhysics,而针对车辆Car的物理引擎是Unreal自己的PhysX引擎。

我不知道AirSim团队进行这样设计的初衷是什么,我推测可能是FastPhysics是针对无人机开发的一款比较快速的物理引擎,毕竟可能无人机的物理模型比车辆要更复杂一点。但我不知道为什么他们不顺便把车辆的仿真模式中也用上自己的FastPhysics,我估计是因为Unreal的PhysX对车辆模拟来说已经足够好了,并且使用上也方便一些。

  • 有关PhysX的简要介绍:UE4物理系统浅析
  • FastPhysics的定义在/AirSim/AirLib/include/physics/FastPhysicsEngine.hpp中

但这样做会产生一个问题,那就是AirSim中的settings中的ClockSpeed参数只对无人机MultiRotor SimMode有效,在车辆Car SimMode下无法有效调节仿真速率,具体问题描述可以参见:微软开源仿真器AirSim ROS接口中的第2节。

在车辆模式应用物理引擎的/AirSim/Unreal/Plugins/AirSim/Source/Vehicles/Car/SimModeCar.cpp文件中,原本是这样写的:

#include "SimModeCar.h"
#include "UObject/ConstructorHelpers.h"

#include "AirBlueprintLib.h"
#include "common/AirSimSettings.hpp"
#include "CarPawnSimApi.h"
#include "AirBlueprintLib.h"
#include "common/Common.hpp"
#include "common/EarthUtils.hpp"
#include "vehicles/car/api/CarRpcLibServer.hpp"

extern CORE_API uint32 GFrameNumber;

void ASimModeCar::BeginPlay()
{
    Super::BeginPlay();

    initializePauseState();
}

void ASimModeCar::initializePauseState()
{
    pause_period_ = 0;
    pause_period_start_ = 0;
    pause(false);
}

bool ASimModeCar::isPaused() const
{
    return current_clockspeed_ == 0;
}

void ASimModeCar::pause(bool is_paused)
{
    if (is_paused)
        current_clockspeed_ = 0;
    else
        current_clockspeed_ = getSettings().clock_speed;

    UAirBlueprintLib::setUnrealClockSpeed(this, current_clockspeed_);
}

void ASimModeCar::continueForTime(double seconds)
{
    pause_period_start_ = ClockFactory::get()->nowNanos();
    pause_period_ = seconds;
    pause(false);
}

void ASimModeCar::continueForFrames(uint32_t frames)
{
    targetFrameNumber_ = GFrameNumber + frames;
    frame_countdown_enabled_ = true;
    pause(false);
}

void ASimModeCar::setupClockSpeed()
{
    current_clockspeed_ = getSettings().clock_speed;

    //setup clock in PhysX
    //在这里我们可以看到,代码直接使用了UAirBlueprintLib的相关方法,也就是使用了Unreal的默认引擎PhysX(目前Unreal新增了Chaos引擎,不知道AirSim以后会不会向Chaos兼容)
    //值得注意的是,在这里的确是设定了clockspeed,但效果仅仅是使整个仿真在”人的感官“下产生了速度变化,
    //仿真输出的时间戳没有变化,不像FastPhysicsEngine那样,仿真输出的时间戳也会相应变化。
    UAirBlueprintLib::setUnrealClockSpeed(this, current_clockspeed_);
    UAirBlueprintLib::LogMessageString("Clock Speed: ", std::to_string(current_clockspeed_), LogDebugLevel::Informational);
}

void ASimModeCar::Tick(float DeltaSeconds)
{
    Super::Tick(DeltaSeconds);
    
    if (pause_period_start_ > 0) {
        if (ClockFactory::get()->elapsedSince(pause_period_start_) >= pause_period_) {
            if (!isPaused())
                pause(true);

            pause_period_start_ = 0;
        }
    }

    if(frame_countdown_enabled_){
        if (targetFrameNumber_ <= GFrameNumber){
            if (! isPaused())
                pause(true);

            frame_countdown_enabled_ = false;
        }
    }
}

//-------------------------------- overrides -----------------------------------------------//
//以下略。

代码中的

UAirBlueprintLib::setUnrealClockSpeed(this, current_clockspeed_);

目的是设置clockspeed,但是对Unreal的PhysX而言,仅仅是使仿真速度在“人的感官”下产生了变化,而仿真输出的信息的时间戳并没有随着ClockSpeed参数的设置而产生更“稀疏”或更“稠密”的变化。

所以我对此文件进行了改写,使车辆模式的仿真也可以使用MultiRotor的FastPhysicsEngine,直接上代码:

#include "SimModeCar.h"
#include "UObject/ConstructorHelpers.h"
#include "Logging/MessageLog.h"
#include "Engine/World.h"
#include "GameFramework/PlayerController.h"
#include <exception>
#include "AirBlueprintLib.h"
#include "common/AirSimSettings.hpp"
#include "CarPawnSimApi.h"
#include "physics/PhysicsBody.hpp"
#include "common/ClockFactory.hpp"
#include "common/EarthUtils.hpp"
#include "common/Common.hpp"
#include <memory>
#include "vehicles/car/api/CarRpcLibServer.hpp"


void ASimModeCar::BeginPlay()
{
    Super::BeginPlay();

    //let base class setup physics world
    initializeForPlay();
}
void ASimModeCar::initializeForPlay()
{
    std::vector<msr::airlib::UpdatableObject*> vehicles;
    for (auto& api : getApiProvider()->getVehicleSimApis())
        vehicles.push_back(api);
    //TODO: directly accept getVehicleSimApis() using generic container

    std::unique_ptr<PhysicsEngineBase> physics_engine = createPhysicsEngine();
    physics_engine_ = physics_engine.get();
    physics_world_.reset(new msr::airlib::PhysicsWorld(std::move(physics_engine),
        vehicles, getPhysicsLoopPeriod()));
}
void ASimModeCar::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
    //remove everything that we created in BeginPlay
    physics_world_.reset();
    //stop physics thread before we dismantle
    //stopAsyncUpdator();

    Super::EndPlay(EndPlayReason);
}
void ASimModeCar::startAsyncUpdator()
{
    physics_world_->startAsyncUpdator();
}

void ASimModeCar::stopAsyncUpdator()
{
    physics_world_->stopAsyncUpdator();
}

long long ASimModeCar::getPhysicsLoopPeriod() const //nanoseconds
{
    return physics_loop_period_;
}
void ASimModeCar::setPhysicsLoopPeriod(long long  period)
{
    physics_loop_period_ = period;
}
std::unique_ptr<ASimModeCar::PhysicsEngineBase> ASimModeCar::createPhysicsEngine()
{
    std::unique_ptr<PhysicsEngineBase> physics_engine;
    std::string physics_engine_name = getSettings().physics_engine_name;
    //选择使用的物理引擎:
    if (physics_engine_name == "")
        physics_engine.reset(); //no physics engine
    else if (physics_engine_name == "FastPhysicsEngine") {
        msr::airlib::Settings fast_phys_settings;
        if (msr::airlib::Settings::singleton().getChild("FastPhysicsEngine", fast_phys_settings)) {
            physics_engine.reset(new msr::airlib::FastPhysicsEngine(fast_phys_settings.getBool("EnableGroundLock", true)));
        }
        else {
            physics_engine.reset(new msr::airlib::FastPhysicsEngine());
        }
    }
    else {
        physics_engine.reset();
        UAirBlueprintLib::LogMessageString("Unrecognized physics engine name: ",  physics_engine_name, LogDebugLevel::Failure);
    }

    return physics_engine;
}

bool ASimModeCar::isPaused() const
{
    return physics_world_->isPaused();
}

void ASimModeCar::pause(bool is_paused)
{
    physics_world_->pause(is_paused);
    UGameplayStatics::SetGamePaused(this->GetWorld(), is_paused);
    pause_physx(true);
}
void ASimModeCar::pause_physx(bool is_paused)
{
    float current_clockspeed_;
    if (is_paused)
        current_clockspeed_ = 0;
    else
        current_clockspeed_ = getSettings().clock_speed;
    UE_LOG(LogTemp,Display,TEXT("*******check point pause:"));
    UAirBlueprintLib::setUnrealClockSpeed(this, current_clockspeed_);
}
void ASimModeCar::continueForTime(double seconds)
{
    if(physics_world_->isPaused())
    {
        physics_world_->pause(false);
        UGameplayStatics::SetGamePaused(this->GetWorld(), false);        
        pause_physx(false);
    }

    physics_world_->continueForTime(seconds);
    while(!physics_world_->isPaused())
    {
        continue; 
    }
    UGameplayStatics::SetGamePaused(this->GetWorld(), true);
}

void ASimModeCar::updateDebugReport(msr::airlib::StateReporterWrapper& debug_reporter)
{
    unused(debug_reporter);
    //we use custom debug reporting for this class
}

void ASimModeCar::Tick(float DeltaSeconds)
{
    { //keep this lock as short as possible
        physics_world_->lock();

        physics_world_->enableStateReport(EnableReport);
        physics_world_->updateStateReport();

        for (auto& api : getApiProvider()->getVehicleSimApis())
            api->updateRenderedState(DeltaSeconds);

        physics_world_->unlock();
    }

    //perform any expensive rendering update outside of lock region
    for (auto& api : getApiProvider()->getVehicleSimApis())
        api->updateRendering(DeltaSeconds);

    Super::Tick(DeltaSeconds);
}

void ASimModeCar::reset()
{
    UAirBlueprintLib::RunCommandOnGameThread([this]() {
        physics_world_->reset();
    }, true);
    
    //no need to call base reset because of our custom implementation
}

std::string ASimModeCar::getDebugReport()
{
    return physics_world_->getDebugReport();
}
void ASimModeCar::setupClockSpeed()
{
    typedef msr::airlib::ClockFactory ClockFactory;

    float clock_speed = getSettings().clock_speed;

    float printout = getSettings().clock_speed;
    //setup clock in PhysX 
    UAirBlueprintLib::setUnrealClockSpeed(this, clock_speed);
    UAirBlueprintLib::LogMessageString("Clock Speed: ", std::to_string(clock_speed), LogDebugLevel::Informational);
    UE_LOG(LogTemp,Display,TEXT("*******check point 3: %f"),printout);
    //setup clock in ClockFactory
    std::string clock_type = getSettings().clock_type;
	//在此设定了时间步长:
    if (clock_type == "ScalableClock") {
        //scalable clock returns interval same as wall clock but multiplied by a scale factor
        ClockFactory::get(std::make_shared<msr::airlib::ScalableClock>(clock_speed == 1 ? 1 : 1 / clock_speed));
        UE_LOG(LogTemp,Display,TEXT("*******check point 3_1: %f"),clock_speed);
    }
    else if (clock_type == "SteppableClock") {
        //steppable clock returns interval that is a constant number irrespective of wall clock
        //we can either multiply this fixed interval by scale factor to speed up/down the clock
        //but that would cause vehicles like quadrotors to become unstable
        //so alternative we use here is instead to scale control loop frequency. The downside is that
        //depending on compute power available, we will max out control loop frequency and therefore can no longer
        //get increase in clock speed

        //Approach 1: scale clock period, no longer used now due to quadrotor instability
        //ClockFactory::get(std::make_shared<msr::airlib::SteppableClock>(
        //static_cast<msr::airlib::TTimeDelta>(getPhysicsLoopPeriod() * 1E-9 * clock_speed)));

        //Approach 2: scale control loop frequency if clock is speeded up
        UE_LOG(LogTemp,Display,TEXT("*******check point 3_2: %f"),clock_speed);
        if (clock_speed >= 1) {
            ClockFactory::get(std::make_shared<msr::airlib::SteppableClock>(
                static_cast<msr::airlib::TTimeDelta>(getPhysicsLoopPeriod() * 1E-9))); //no clock_speed multiplier

            setPhysicsLoopPeriod(getPhysicsLoopPeriod() / static_cast<long long>(clock_speed));
        }
        else {
            //for slowing down, this don't generate instability
            ClockFactory::get(std::make_shared<msr::airlib::SteppableClock>(
                static_cast<msr::airlib::TTimeDelta>(getPhysicsLoopPeriod() * 1E-9 * clock_speed)));
        }
    }
    else
        throw std::invalid_argument(common_utils::Utils::stringf(
            "clock_type %s is not recognized", clock_type.c_str()));
}

//-------------------------------- overrides -----------------------------------------------//
//以下略

显然对应的hpp也需要修改,全部改动后的代码在:GimpelZhang/AirSim

  • 4
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

寒墨阁

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值