CameraAPI issue

SU5.1

Version: 1.17.34.0

Frequency: Consistently

Severity: High

Subject: Possible Camera API Issue – Roll Applied When Using WORLD Reference with ALL_TARGETED

When calling SimConnect_CameraSet() with:

  • Position/Rotation Referential: WORLD
  • Mask: SIMCONNECT_CAMERA_DATA_MASK.ALL_TARGETED

I would expect the camera to remain level with the world horizon (bank = 0) while tracking the target.

Instead, the camera appears to inherit the aircraft’s roll angle. During turns or when the aircraft is banked, the camera rolls with the aircraft even though the referential is WORLD.

My expectation is that:

  • WORLD referential should keep the camera horizon level.
  • ALL_TARGETED should only cause the camera to track the target, not inherit its roll.

Could you please confirm whether this is:

  1. The intended behavior,
  2. A limitation of the current Camera API, or
  3. A bug?

If this is expected behavior, is there another supported way to create a targeted camera that tracks an aircraft while always remaining level with the world horizon?

Thank you for your assistance.

ALL_ROTATION mask can be used, only target relative position should be calculated each frame by script. you can test it as Chase camera in Airshow Assistant v4 if you have it.

can’t promise this peace of code works correctly, code was extracted from my project by AI:

static const char* CAMERA_CLIENT_ID = "My Chase Camera";

static bool g_cameraRequested = false;
static bool g_cameraAcquired = false;
static bool g_gameControlled = false;

static SIMCONNECT_DATA_LATLONALT g_chasePos = {};
static bool g_chaseValid = false;

static double g_prevLat = 0.0;
static double g_prevLon = 0.0;
static double g_prevAlt = 0.0;
static bool g_prevValid = false;

static double g_dirEast = 0.0;
static double g_dirNorth = 0.0;
static double g_dirUp = 0.0;

static double degToRad(double v) { return v * 3.14159265358979323846 / 180.0; }
static double radToDeg(double v) { return v * 180.0 / 3.14159265358979323846; }

static double clamp(double v, double lo, double hi) {
    return v < lo ? lo : (v > hi ? hi : v);
}

static double bearingRad(double lat1, double lon1, double lat2, double lon2) {
    double y = sin(degToRad(lon2 - lon1)) * cos(degToRad(lat2));
    double x =
        cos(degToRad(lat1)) * sin(degToRad(lat2)) -
        sin(degToRad(lat1)) * cos(degToRad(lat2)) * cos(degToRad(lon2 - lon1));
    return atan2(y, x);
}

void HandleCameraStatus(SIMCONNECT_RECV_CAMERA_STATUS* msg) {
    g_gameControlled = msg->bGameControlled != 0;
    g_cameraAcquired = msg->acquiredState == SIMCONNECT_CAMERA_ACQUIRED;
}

void UpdateChaseCamera(
    HANDLE hSimConnect,
    double dt,
    double aircraftLatDeg,
    double aircraftLonDeg,
    double aircraftAltM,
    double aircraftHeadingRad,
    double distanceM,
    double smooth01,
    double pitchOffsetDeg,
    double headingOffsetDeg,
    double fovDeg)
{
    if (!g_cameraRequested) {
        if (SimConnect_CameraAcquire(hSimConnect, CAMERA_CLIENT_ID) != S_OK)
            return;

        SimConnect_CameraGetStatus(hSimConnect);
        g_cameraRequested = true;
        return;
    }

    if (!g_cameraAcquired || g_gameControlled)
        return;

    dt = clamp(dt, 0.0, 1.0);
    distanceM = max(distanceM * 0.5, 3.0);

    if (g_prevValid && dt > 0.0001) {
        double lonScale = 111111.0 * max(0.2, cos(degToRad(aircraftLatDeg)));
        double east = (aircraftLonDeg - g_prevLon) * lonScale / dt;
        double north = (aircraftLatDeg - g_prevLat) * 111111.0 / dt;
        double up = (aircraftAltM - g_prevAlt) / dt;
        double speed = sqrt(east * east + north * north + up * up);

        if (speed > 1.0) {
            g_dirEast = east / speed;
            g_dirNorth = north / speed;
            g_dirUp = up / speed;
        }
    }

    if (g_dirEast == 0.0 && g_dirNorth == 0.0 && g_dirUp == 0.0) {
        g_dirEast = sin(aircraftHeadingRad);
        g_dirNorth = cos(aircraftHeadingRad);
    }

    g_prevLat = aircraftLatDeg;
    g_prevLon = aircraftLonDeg;
    g_prevAlt = aircraftAltM;
    g_prevValid = true;

    double lonScale = 111111.0 * max(0.2, cos(degToRad(aircraftLatDeg)));
    double targetCamLat = aircraftLatDeg - g_dirNorth * distanceM / 111111.0;
    double targetCamLon = aircraftLonDeg - g_dirEast * distanceM / lonScale;
    double targetCamAlt = aircraftAltM - g_dirUp * distanceM;

    if (!g_chaseValid) {
        g_chasePos = { targetCamLat, targetCamLon, targetCamAlt };
        g_chaseValid = true;
    }

    double lag = clamp(dt / (0.15 + 1.5 * clamp(smooth01, 0.0, 1.0)), 0.0, 1.0);
    g_chasePos.Latitude += (targetCamLat - g_chasePos.Latitude) * lag;
    g_chasePos.Longitude += (targetCamLon - g_chasePos.Longitude) * lag;
    g_chasePos.Altitude += (targetCamAlt - g_chasePos.Altitude) * lag;

    double horizontalM = sqrt(
        pow((aircraftLatDeg - g_chasePos.Latitude) * 111111.0, 2.0) +
        pow((aircraftLonDeg - g_chasePos.Longitude) * lonScale, 2.0));

    double verticalM = aircraftAltM - g_chasePos.Altitude;
    double aimBearing = bearingRad(g_chasePos.Latitude, g_chasePos.Longitude, aircraftLatDeg, aircraftLonDeg);

    SIMCONNECT_DATA_CAMERA camera = {};
    camera.PositionReferential = SIMCONNECT_POSITION_REFERENTIAL_WORLD;
    camera.Position.x = g_chasePos.Latitude;
    camera.Position.y = g_chasePos.Longitude;
    camera.Position.z = g_chasePos.Altitude;

    camera.RotationReferential = SIMCONNECT_POSITION_REFERENTIAL_WORLD;
    camera.TargetedPos.x = aircraftLatDeg;
    camera.TargetedPos.y = aircraftLonDeg;
    camera.TargetedPos.z = aircraftAltM;

    camera.Pbh.Pitch = (float)(radToDeg(atan2(-verticalM, max(horizontalM, 1e-6))) + pitchOffsetDeg);
    camera.Pbh.Bank = 0.0f;
    camera.Pbh.Heading = (float)(radToDeg(aimBearing) + headingOffsetDeg);
    camera.Fov = degToRad(fovDeg);

    SimConnect_CameraSet(
        hSimConnect,
        camera,
        SIMCONNECT_CAMERA_DATA_MASK_ALL_ROTATION);
}

void ReleaseChaseCamera(HANDLE hSimConnect) {
    if (g_cameraRequested || g_cameraAcquired)
        SimConnect_CameraRelease(hSimConnect, nullptr);

    g_cameraRequested = false;
    g_cameraAcquired = false;
    g_gameControlled = false;
    g_chaseValid = false;
    g_prevValid = false;
}

This report should be moved to [MSFS 2024] Bug Reports - SimConnect

Hello @Voss1917

This is a design flaw.
Depending on the camera you want, you could want different orientation of the camera’s up vector.
We will review this and try to come up with a solution.

Regards,
Sylvain

@Voss1917 as temporary workaround I send a pause to game right before entering the Camera and send un pause when the shot is finished,
Quite lame, but it just works , as soon as the plane is leveled (with the game pause you can also send to the airplane pitch/yaw/bank to zero too)