MotionSystems

Disclaimer

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

Liability

THE MOTION SYSTEMS COMPANY AND ITS DISTRIBUTORS ARE NOT LIABLE FOR ANY DAMAGE, INJURIES OR EVEN DEATH RESULTING FROM ANY ELEMENTS NOT PROVIDED BY THE MOTION SYSTEMS COMPANY AND/OR INCORRECT USE OR ASSEMBLY OF THE PRODUCT.

All trademarks, brands and logos are copyright of their respective owners.

Examples presented in this document and the plugin have been made in Unreal Engine 4.14 and 4.15 and might not work correctly with different versions of the Unreal Engine.

Plugin integration

Please follow below steps in order to include ForceSeatMI plugin into your project.

  1. Create C++ Unreal Engine project
  2. Inside root directory of your project create Plugins directory
  3. Copy ForceSeatMI plugin folder into Plugins directory
  4. Launch Unreal Engine editor
  5. Add ForceSeatMI plugin dependency to your project inside YourProject.Build.cs
    PublicDependencyModuleNames.AddRange(new string[] { "Core", "CoreUObject", "Engine", "InputCore", "ForceSeatMI" });
  6. UE 4.15+ users: ForceSeatMI_VehicleTelemetry requires PhysXVehicles so make sure that this module it is added to your project
  7. After everything is built and compiled you can start using ForceSeatMI in your application!

ForceSeatMI plugin uses DLL which is installed as part of the ForceSeatPM software. Make sure that you have ForceSeatPM installed on your computer.

Application: top table positioning

Examples:

For both examples, use built-in ForceSeatPM profile SDK - Positioning or SDK - Pos - Low FPS.

Positioning application requires usage of raw ForceSeatMI API. Typical operation routine consists of following steps:

  1. Create a pointer member variable inside your class which will point to an instance of IForceSeatMI_API:
    IForceSeatMI_API* m_api;
  2. Initialize it in class constructor:
    m_api(&IForceSeatMI::Get().GetAPI())
  3. When simulation starts call (e.g. in BeginPlay() method of your APawn implementation):
    m_api->BeginMotionControl();
  4. The SIM should send positioning data in constant intervals using one of the following functions:
    m_api->SendTopTablePosLog(...);
    m_api->SendTopTablePosPhy(...);
    m_api->SendTopTableMatrixPhy(...);
     
  5. At the end of simulation call:
    m_api->EndMotionControl();

Code

Below code comes from TableLogPos_Unreal example.

 
ATableLogPos_UnrealPawn::ATableLogPos_UnrealPawn()
  : CurrentDrawingHeave(0)
  , CurrentDrawingPitch(0)
  , CurrentDrawingRoll(0)
{
  PrimaryActorTick.bCanEverTick = true;
 
  // Create platform's board subobject
  Board = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Board0"));
  Board->BodyInstance.SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
  Board->SetSimulatePhysics(false);
  Board->SetNotifyRigidBodyCollision(true);
 
  // Create platform's base subobject
  Base = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Base0"));
  Base->BodyInstance.SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
  Base->SetSimulatePhysics(false);
  Base->SetNotifyRigidBodyCollision(true);
 
  // Create platform's shaft subobject
  Shaft = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Shaft0"));
  Shaft->BodyInstance.SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
  Shaft->SetSimulatePhysics(false);
  Shaft->SetNotifyRigidBodyCollision(true);
 
  // Create shaft's head subobject
  ShaftHead = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("ShaftHead0"));
  ShaftHead->BodyInstance.SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
  ShaftHead->SetSimulatePhysics(false);
  ShaftHead->SetNotifyRigidBodyCollision(true);
 
  // Create root subobject
  Root = CreateDefaultSubobject<UStaticMeshComponent>(TEXT("Root0"));
  Root->BodyInstance.SetCollisionProfileName(UCollisionProfile::PhysicsActor_ProfileName);
  Root->SetSimulatePhysics(false);
  Root->SetNotifyRigidBodyCollision(true);
 
  RootComponent = Root;
 
  // ForceSeatMI - BEGIN
  // Initialize platform position structure
  memset(&PlatformPosition, 0, sizeof(PlatformPosition));
  PlatformPosition.structSize = sizeof(PlatformPosition);
 
  // The demo program is able to provide pause, position and speed limit
  PlatformPosition.maxSpeed = PLATFORM_MAX_SPEED;
  PlatformPosition.mask     = FSMI_POS_BIT_STATE | FSMI_POS_BIT_POSITION | FSMI_POS_BIT_MAX_SPEED;
  // ForceSeatMI - END
}
 
void ATableLogPos_UnrealPawn::Tick(float DeltaTime)
{
  Super::Tick(DeltaTime);
 
  // Update board's origin transformation (in game) with current heave, pitch and roll values
  FVector BoardLocation = BoardOriginTransform.GetLocation();
  FQuat BoardRotation = BoardOriginTransform.GetRotation();
  BoardLocation.Z += CurrentDrawingHeave;
  BoardRotation.X += CurrentDrawingPitch;
  BoardRotation.Y += CurrentDrawingRoll;
 
  // Set calculated transformation for board subobject
  Board->SetRelativeLocationAndRotation(BoardLocation, BoardRotation);
 
  // Update shaft's origin transformation  (in game) with current heave value
  FVector ShaftLocation = ShaftOriginTransform.GetLocation();
  ShaftLocation.Z += CurrentDrawingHeave;
 
  // Set calculated transformation for shaft subobject
  Shaft->SetRelativeLocationAndRotation(ShaftLocation, ShaftOriginTransform.GetRotation());
 
  // Update shaft's head origin transformation  (in game) with current heave value
  FVector ShaftHeadLocation = ShaftHeadOriginTransform.GetLocation();
  ShaftHeadLocation.Z += CurrentDrawingHeave;
 
  // Set calculated transformation for shaft's head subobject
  ShaftHead->SetRelativeLocationAndRotation(ShaftHeadLocation, ShaftHeadOriginTransform.GetRotation());
 
  // ForceSeatMI - BEGIN
  SendCoordinatesToPlatform();
  // ForceSeatMI - END
}
 
void ATableLogPos_UnrealPawn::BeginPlay()
{
  Super::BeginPlay();
 
  // ForceSeatMI - BEGIN
  if (FSMI_True == IForceSeatMI::Get().GetAPI().BeginMotionControl())
  {
    SendCoordinatesToPlatform();
  }
  // ForceSeatMI - END
 
  // Get origin transformations for movable subobjects
  BoardOriginTransform = Board->GetRelativeTransform();
  ShaftOriginTransform = Shaft->GetRelativeTransform();
  ShaftHeadOriginTransform = ShaftHead->GetRelativeTransform();
}
 
void ATableLogPos_UnrealPawn::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
  Super::EndPlay(EndPlayReason);
 
  // ForceSeatMI - BEGIN
  IForceSeatMI::Get().GetAPI().EndMotionControl();
  // ForceSeatMI - END
}
 
void ATableLogPos_UnrealPawn::SendCoordinatesToPlatform()
{
  // ForceSeatMI - BEGIN
  PlatformPosition.state = FSMI_STATE_NO_PAUSE;
  PlatformPosition.roll = static_cast<FSMI_INT16>(FMath::Clamp<float>(CurrentDrawingRoll / DRAWING_ROLL_MAX  * PLATFORM_POSITION_LOGIC_MAX, PLATFORM_POSITION_LOGIC_MIN, PLATFORM_POSITION_LOGIC_MAX));
  PlatformPosition.pitch = static_cast<FSMI_INT16>(FMath::Clamp<float>(CurrentDrawingPitch / DRAWING_PITCH_MAX * PLATFORM_POSITION_LOGIC_MAX, PLATFORM_POSITION_LOGIC_MIN, PLATFORM_POSITION_LOGIC_MAX));
  PlatformPosition.heave = static_cast<FSMI_INT16>(FMath::Clamp<float>(CurrentDrawingHeave / DRAWING_HEAVE_MAX * PLATFORM_POSITION_LOGIC_MAX, PLATFORM_POSITION_LOGIC_MIN, PLATFORM_POSITION_LOGIC_MAX));
 
  IForceSeatMI::Get().GetAPI().SendTopTablePosLog(&PlatformPosition);
  // ForceSeatMI - END
}
 

Application: vehicle simulation

Examples: Telemetry_Veh_Unreal (use built-in ForceSeatPM profile SDK - Vehicle Telemetry)

For vehicle simulation application IForceSeatMI_VehicleTelemetry helper interface can be used. IForceSeatMI_VehicleTelemetry requires PhysXVehicles and extracts automatically all necessary data from APawn and UWheeledVehicleMovementComponent objects. Typical operation routine consists of following steps:

  1. Create a pointer member variable inside your class which will point to an instance of IForceSeatMI_VehicleTelemetry:
    IForceSeatMI_VehicleTelemetry* m_veh;
  2. Initialize it in class constructor:
    m_veh(&IForceSeatMI::Get().GetVehicleTelemetry())
  3. When simulation starts call (e.g. in BeginPlay() method of your APawn implementation):
    m_veh->Begin();();
  4. The SIM should send telemetry data in constant intervals using following function:
    m_veh->Tick(...);
  5. At the end of simulation call:
    m_veh->End();

Code

Below code comes from Telemetry_Veh_Unreal example.

 
ATelemetry_Veh_UnrealPawn::ATelemetry_Veh_UnrealPawn()
{
  // Car mesh
  static ConstructorHelpers::FObjectFinder<USkeletalMesh> CarMesh(TEXT("/Game/Vehicle/Sedan/Sedan_SkelMesh.Sedan_SkelMesh"));
  GetMesh()->SetSkeletalMesh(CarMesh.Object);
 
  static ConstructorHelpers::FClassFinder<UObject> AnimBPClass(TEXT("/Game/Vehicle/Sedan/Sedan_AnimBP"));
  GetMesh()->SetAnimInstanceClass(AnimBPClass.Class);
 
  // Simulation
  UWheeledVehicleMovementComponent4W* Vehicle4W = CastChecked<UWheeledVehicleMovementComponent4W>(GetVehicleMovement());
 
  check(Vehicle4W->WheelSetups.Num() == 4);
 
  Vehicle4W->WheelSetups[0].WheelClass = UTelemetry_Veh_UnrealWheelFront::StaticClass();
  Vehicle4W->WheelSetups[0].BoneName = FName("Wheel_Front_Left");
  Vehicle4W->WheelSetups[0].AdditionalOffset = FVector(0.f, -12.f, 0.f);
 
  Vehicle4W->WheelSetups[1].WheelClass = UTelemetry_Veh_UnrealWheelFront::StaticClass();
  Vehicle4W->WheelSetups[1].BoneName = FName("Wheel_Front_Right");
  Vehicle4W->WheelSetups[1].AdditionalOffset = FVector(0.f, 12.f, 0.f);
 
  Vehicle4W->WheelSetups[2].WheelClass = UTelemetry_Veh_UnrealWheelRear::StaticClass();
  Vehicle4W->WheelSetups[2].BoneName = FName("Wheel_Rear_Left");
  Vehicle4W->WheelSetups[2].AdditionalOffset = FVector(0.f, -12.f, 0.f);
 
  Vehicle4W->WheelSetups[3].WheelClass = UTelemetry_Veh_UnrealWheelRear::StaticClass();
  Vehicle4W->WheelSetups[3].BoneName = FName("Wheel_Rear_Right");
  Vehicle4W->WheelSetups[3].AdditionalOffset = FVector(0.f, 12.f, 0.f);
 
  // Create a spring arm component
  SpringArm = CreateDefaultSubobject<USpringArmComponent>(TEXT("SpringArm0"));
  SpringArm->TargetOffset = FVector(0.f, 0.f, 200.f);
  SpringArm->SetRelativeRotation(FRotator(-15.f, 0.f, 0.f));
  SpringArm->SetupAttachment(RootComponent);
  SpringArm->TargetArmLength = 600.0f;
  SpringArm->bEnableCameraRotationLag = true;
  SpringArm->CameraRotationLagSpeed = 7.f;
  SpringArm->bInheritPitch = false;
  SpringArm->bInheritRoll = false;
 
  // Create camera component 
  Camera = CreateDefaultSubobject<UCameraComponent>(TEXT("Camera0"));
  Camera->SetupAttachment(SpringArm, USpringArmComponent::SocketName);
  Camera->bUsePawnControlRotation = false;
  Camera->FieldOfView = 90.f;
 
  bInReverseGear = false;
}
 
void ATelemetry_Veh_UnrealPawn::Tick(float Delta)
{
  Super::Tick(Delta);
 
  // ForceSeatMI - BEGIN
  IForceSeatMI::Get().GetVehicleTelemetry().Tick(*this, *GetVehicleMovement(), Delta, false/* no pause */);
  // ForceSeatMI - END
}
 
void ATelemetry_Veh_UnrealPawn::BeginPlay()
{
  Super::BeginPlay();
 
  // ForceSeatMI - BEGIN
  IForceSeatMI::Get().GetVehicleTelemetry().Begin();
  // ForceSeatMI - END
}
 

Application: flight simulation

Examples: Telemetry_Fly_Unreal (use built-in ForceSeatPM profile SDK - Plane Telemetry)

For flight simulation, a raw IForceSeatMI_API or IForceSeatMI_PlaneTelemetry helper interface can be used. IForceSeatMI_PlaneTelemetry automatically extracts all necessary data from APawn object and then calculates missing information like accelerations. Typical operation routine consists of following steps:

  1. Create a pointer member variable inside your class which will point to an instance of IForceSeatMI_PlaneTelemetry:
    IForceSeatMI_PlaneTelemetry* m_fly;
  2. Initialize it in class constructor:
    m_fly(&IForceSeatMI::Get().GetPlaneTelemetry())
  3. When simulation starts call (e.g. in BeginPlay() method of your APawn implementation):
    m_fly->Begin();();
  4. The SIM should send telemetry data in constant intervals using following function:
    m_fly->Tick(...);
  5. At the end of simulation call:
    m_fly->End();

Code

Below code comes from Telemetry_Fly_Unreal example.

 
void ATelemetry_Fly_UnrealPawn::Tick(float DeltaSeconds)
{
  const FVector LocalMove = FVector(CurrentForwardSpeed * DeltaSeconds, 0.f, 0.f);
 
  // Move plan forwards (with sweep so we stop when we collide with things)
  AddActorLocalOffset(LocalMove, true);
 
  // Calculate change in rotation this frame
  FRotator DeltaRotation(0,0,0);
  DeltaRotation.Pitch = CurrentPitchSpeed * DeltaSeconds;
  DeltaRotation.Yaw = CurrentYawSpeed * DeltaSeconds;
  DeltaRotation.Roll = CurrentRollSpeed * DeltaSeconds;
 
  // Rotate plane
  AddActorLocalRotation(DeltaRotation);
 
  // Call any parent class Tick implementation
  Super::Tick(DeltaSeconds);
 
  // ForceSeatMI - BEGIN
  IForceSeatMI::Get().GetPlaneTelemetry().Tick(*this, DeltaSeconds, false/* no pause */);
  // ForceSeatMI - END
}
 
void ATelemetry_Fly_UnrealPawn::BeginPlay()
{
  Super::BeginPlay();
 
  // ForceSeatMI - BEGIN
  IForceSeatMI::Get().GetPlaneTelemetry().Begin();
  // ForceSeatMI - END
}