xp12camera
Owner: IIIlllIIIllI URL: git@github.com:nyangkosense/xp12camera.git
13
Commit 83d48f7366f60137edf6af2223d069116ec1286f by SM <seb.michalk@gmail.com> on 2025-06-25 19:14:53 +0200
diff --git a/FLIR_Camera.cpp b/FLIR_Camera.cpp
index 9e9cca2..b1d61bd 100644
--- a/FLIR_Camera.cpp
+++ b/FLIR_Camera.cpp
@@ -5,9 +5,9 @@
* Features:
* - Real camera positioning under aircraft (belly-mounted)
* - True optical zoom with zoom parameter
- * - Pan/tilt camera controls
- * - Target acquisition and tracking
- * - Thermal overlay rendering
+ * - Pan/tilt camera controls with mouse
+ * - Arbitrary point lock-on system
+ * - Military-style targeting reticles
*
*/
@@ -24,11 +24,7 @@
#include "XPLMGraphics.h"
#include "XPLMProcessing.h"
#include "XPLMMenus.h"
- #include "XPLMScenery.h"
- #include "XPLMPlanes.h"
-#include "XPLMNavigation.h"
-#include "XPLMInstance.h"
-#include "XPLMWeather.h"
+ #include "FLIR_LockOn.h"
// OpenGL includes for MinGW
#include <windows.h>
@@ -69,59 +65,9 @@ static float gMouseSensitivity = 0.2f; // Mouse sensitivity multiplier
// Thermal view settings
static int gThermalMode = 1; // 0=Off, 1=White Hot, 2=Enhanced
-
- // Target tracking and object detection
- static int gTargetLocked = 0;
-static float gFocusDistance = 1000.0f; // Distance to focused target
- static float gTargetX = 0.0f; // World position of locked target
- static float gTargetY = 0.0f;
- static float gTargetZ = 0.0f;
- static int gLockedTargetIndex = -1; // Index of locked target in heat sources
-
- // Aircraft tracking arrays (up to 20 aircraft)
- static float gAircraftPositions[20][3]; // x, y, z positions
- static float gAircraftEngineTemp[20]; // Engine temperatures
- static float gAircraftDistance[20]; // Distance from camera
-static int gAircraftVisible[20]; // Visibility in thermal
-static int gAircraftCount = 0;
-
-// Environmental factors
-static float gCurrentVisibility = 10000.0f;
-static float gCurrentTemp = 15.0f;
-static float gCurrentWindSpeed = 0.0f;
-static int gIsNight = 0;
-
-// Heat source simulation
-typedef struct {
- float x, y, z; // World position
- float intensity; // Heat intensity (0-1)
- float size; // Heat source size
- int type; // 0=aircraft, 1=ship, 2=vehicle, 3=building
- float lastUpdate; // Last update time
-} HeatSource;
-static HeatSource gHeatSources[50];
-static int gHeatSourceCount = 0;
-
- // Additional datarefs for enhanced detection
- static XPLMDataRef gEngineRunning = NULL;
- static XPLMDataRef gEngineN1 = NULL;
-static XPLMDataRef gEngineEGT = NULL;
-static XPLMDataRef gGroundTemperature = NULL;
-static XPLMDataRef gWeatherVisibility = NULL;
-static XPLMDataRef gCloudCoverage = NULL;
-static XPLMDataRef gAmbientTemperature = NULL;
-static XPLMDataRef gWindSpeed = NULL;
-static XPLMDataRef gTimeOfDay = NULL;
-static XPLMDataRef gAIAircraftX = NULL;
-static XPLMDataRef gAIAircraftY = NULL;
-static XPLMDataRef gAIAircraftZ = NULL;
-static XPLMDataRef gAIAircraftCount = NULL;
-static XPLMDataRef gLocalDate = NULL;
-static XPLMDataRef gZuluTime = NULL;
-static XPLMDataRef gLatitude = NULL;
-static XPLMDataRef gLongitude = NULL;
-static XPLMDataRef gAltitude = NULL;
+// Dataref to control HUD visibility
+static XPLMDataRef gManipulatorDisabled = NULL;
// Function declarations
static void ActivateFLIRCallback(void* inRefcon);
@@ -142,12 +88,7 @@ static void FocusLockCallback(void* inRefcon);
int inIsBefore,
void* inRefcon);
-static void UpdateEnvironmentalFactors(void);
-static void UpdateHeatSources(void);
-static void DetectAircraft(void);
-static float CalculateHeatIntensity(float engineTemp, float distance, int engineRunning);
static void DrawRealisticThermalOverlay(void);
-static float GetDistanceToCamera(float x, float y, float z);
// Plugin lifecycle functions
PLUGIN_API int XPluginStart(char* outName, char* outSig, char* outDesc)
@@ -155,7 +96,7 @@ static float GetDistanceToCamera(float x, float y, float z);
strcpy(outName, "FLIR Camera System");
strcpy(outSig, "flir.camera.system");
strcpy(outDesc, "Realistic FLIR camera with zoom and thermal overlay");
-
+
// Get aircraft position datarefs
gPlaneX = XPLMFindDataRef("sim/flightmodel/position/local_x");
gPlaneY = XPLMFindDataRef("sim/flightmodel/position/local_y");
@@ -163,29 +104,13 @@ static float GetDistanceToCamera(float x, float y, float z);
gPlaneHeading = XPLMFindDataRef("sim/flightmodel/position/psi");
gPlanePitch = XPLMFindDataRef("sim/flightmodel/position/theta");
gPlaneRoll = XPLMFindDataRef("sim/flightmodel/position/phi");
-
- // Additional datarefs for thermal detection
- gEngineRunning = XPLMFindDataRef("sim/flightmodel/engine/ENGN_running");
- gEngineN1 = XPLMFindDataRef("sim/flightmodel/engine/ENGN_N1_");
- gEngineEGT = XPLMFindDataRef("sim/flightmodel/engine/ENGN_EGT_c");
- gGroundTemperature = XPLMFindDataRef("sim/weather/temperature_sealevel_c");
- gWeatherVisibility = XPLMFindDataRef("sim/weather/visibility_reported_m");
- gCloudCoverage = XPLMFindDataRef("sim/weather/cloud_coverage[0]");
- gAmbientTemperature = XPLMFindDataRef("sim/weather/temperature_ambient_c");
- gWindSpeed = XPLMFindDataRef("sim/weather/wind_speed_kt[0]");
- gTimeOfDay = XPLMFindDataRef("sim/time/local_time_sec");
- gAIAircraftX = XPLMFindDataRef("sim/multiplayer/position/plane1_x");
- gAIAircraftY = XPLMFindDataRef("sim/multiplayer/position/plane1_y");
- gAIAircraftZ = XPLMFindDataRef("sim/multiplayer/position/plane1_z");
- gAIAircraftCount = XPLMFindDataRef("sim/operation/prefs/mult_max");
-
- // Additional datarefs for HUD display
- gLocalDate = XPLMFindDataRef("sim/time/local_date_days");
- gZuluTime = XPLMFindDataRef("sim/time/zulu_time_sec");
- gLatitude = XPLMFindDataRef("sim/flightmodel/position/latitude");
- gLongitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
- gAltitude = XPLMFindDataRef("sim/flightmodel/position/elevation");
-
+
+ // Dataref for HUD control
+ gManipulatorDisabled = XPLMFindDataRef("sim/operation/prefs/misc/manipulator_disabled");
+
+ // Initialize lock-on system
+ InitializeLockOnSystem();
+
// Register hotkeys
gActivateKey = XPLMRegisterHotKey(XPLM_VK_F9, xplm_DownFlag,
"Activate FLIR Camera",
@@ -214,16 +139,7 @@ static float GetDistanceToCamera(float x, float y, float z);
gTiltDownKey = XPLMRegisterHotKey(XPLM_VK_DOWN, xplm_DownFlag,
"FLIR Tilt Down",
TiltDownCallback, NULL);
-
- // Register thermal overlay drawing callback - use objects phase for better integration
- // Register 2D drawing callback for overlay - per SDK guidance for overlays/annotations
- // Only register when camera is active to minimize performance overhead
- // Drawing callback will be registered dynamically when camera is activated
-
- XPLMDebugString("FLIR Camera System: Plugin loaded successfully\n");
- XPLMDebugString("FLIR Camera System: Press F9 to activate camera\n");
- XPLMDebugString("FLIR Camera System: MOUSE for smooth pan/tilt, +/- for zoom, arrows for fine adjust, T for thermal, SPACE for target lock\n");
-
+
gThermalToggleKey = XPLMRegisterHotKey(XPLM_VK_T, xplm_DownFlag,
"FLIR Thermal Toggle",
ThermalToggleCallback, NULL);
@@ -231,9 +147,11 @@ static float GetDistanceToCamera(float x, float y, float z);
gFocusLockKey = XPLMRegisterHotKey(XPLM_VK_SPACE, xplm_DownFlag,
"FLIR Focus/Lock Target",
FocusLockCallback, NULL);
-
- // Note: For text display, use FlyWithLua to read standard X-Plane datarefs
-
+
+ XPLMDebugString("FLIR Camera System: Plugin loaded successfully\n");
+ XPLMDebugString("FLIR Camera System: Press F9 to activate camera\n");
+ XPLMDebugString("FLIR Camera System: MOUSE for smooth pan/tilt, +/- for zoom, arrows for fine adjust, T for thermal, SPACE for lock-on\n");
+
return 1;
}
@@ -249,13 +167,13 @@ static float GetDistanceToCamera(float x, float y, float z);
if (gTiltDownKey) XPLMUnregisterHotKey(gTiltDownKey);
if (gThermalToggleKey) XPLMUnregisterHotKey(gThermalToggleKey);
if (gFocusLockKey) XPLMUnregisterHotKey(gFocusLockKey);
-
+
// Stop camera control if active
if (gCameraActive) {
XPLMDontControlCamera();
gCameraActive = 0;
}
-
+
XPLMDebugString("FLIR Camera System: Plugin stopped\n");
}
@@ -270,15 +188,16 @@ static float GetDistanceToCamera(float x, float y, float z);
// Activate FLIR camera
XPLMDebugString("FLIR Camera System: Activating camera\n");
- // Switch to external view first
- // XPLMCommandButtonPress(xplm_joy_v_fr1);
- // XPLMCommandButtonRelease(xplm_joy_v_fr1);
-
// Take camera control
XPLMControlCamera(xplm_ControlCameraUntilViewChanges, FLIRCameraFunc, NULL);
gCameraActive = 1;
- // Register 2D drawing callback for overlays (per SDK guidance)
+ // Set manipulator disabled for HUD control
+ if (gManipulatorDisabled) {
+ XPLMSetDatai(gManipulatorDisabled, 1);
+ }
+
+ // Register 2D drawing callback for overlays
if (!gDrawCallbackRegistered) {
XPLMRegisterDrawCallback(DrawThermalOverlay, xplm_Phase_Window, 0, NULL);
gDrawCallbackRegistered = 1;
@@ -290,6 +209,15 @@ static float GetDistanceToCamera(float x, float y, float z);
// Deactivate camera
XPLMDontControlCamera();
gCameraActive = 0;
+
+ // Re-enable manipulator
+ if (gManipulatorDisabled) {
+ XPLMSetDatai(gManipulatorDisabled, 0);
+ }
+
+ // Disable lock-on when camera deactivated
+ DisableLockOn();
+
// Unregister drawing callback to save performance
if (gDrawCallbackRegistered) {
XPLMUnregisterDrawCallback(DrawThermalOverlay, xplm_Phase_Window, 0, NULL);
@@ -348,7 +276,7 @@ static float GetDistanceToCamera(float x, float y, float z);
static void PanLeftCallback(void* inRefcon)
{
- if (gCameraActive) {
+ if (gCameraActive && !IsLockOnActive()) { // Only when not locked
gCameraPan -= 0.5f; // Very sensitive control
if (gCameraPan < -180.0f) gCameraPan += 360.0f;
char msg[256];
@@ -359,7 +287,7 @@ static float GetDistanceToCamera(float x, float y, float z);
static void PanRightCallback(void* inRefcon)
{
- if (gCameraActive) {
+ if (gCameraActive && !IsLockOnActive()) { // Only when not locked
gCameraPan += 0.5f; // Very sensitive control
if (gCameraPan > 180.0f) gCameraPan -= 360.0f;
char msg[256];
@@ -370,7 +298,7 @@ static float GetDistanceToCamera(float x, float y, float z);
static void TiltUpCallback(void* inRefcon)
{
- if (gCameraActive) {
+ if (gCameraActive && !IsLockOnActive()) { // Only when not locked
gCameraTilt = fminf(gCameraTilt + 0.5f, 45.0f); // Very sensitive control
char msg[256];
sprintf(msg, "FLIR Camera System: Tilt %.1f degrees\n", gCameraTilt);
@@ -380,7 +308,7 @@ static float GetDistanceToCamera(float x, float y, float z);
static void TiltDownCallback(void* inRefcon)
{
- if (gCameraActive) {
+ if (gCameraActive && !IsLockOnActive()) { // Only when not locked
gCameraTilt = fmaxf(gCameraTilt - 0.5f, -90.0f); // Very sensitive control
char msg[256];
sprintf(msg, "FLIR Camera System: Tilt %.1f degrees\n", gCameraTilt);
@@ -398,911 +326,209 @@ static float GetDistanceToCamera(float x, float y, float z);
XPLMDebugString(msg);
}
}
+
+static void FocusLockCallback(void* inRefcon)
+{
+ if (gCameraActive) {
+ if (!IsLockOnActive()) {
+ // Lock on to current camera direction at 1000m distance
+ SetArbitraryLockPoint(gCameraPan, gCameraTilt, 1000.0f);
+ XPLMDebugString("FLIR Camera System: Lock-on activated\n");
+ } else {
+ // Disable lock-on
+ DisableLockOn();
+ XPLMDebugString("FLIR Camera System: Lock-on disabled\n");
+ }
+ }
+}
- /*
- * FLIRCameraFunc
- *
- * The main camera control function - positions camera under aircraft
- * like a real belly-mounted FLIR system
- */
+ // Camera control function
static int FLIRCameraFunc(XPLMCameraPosition_t* outCameraPosition,
int inIsLosingControl,
void* inRefcon)
{
- if (outCameraPosition && !inIsLosingControl) {
- // Get current aircraft position and orientation
- float planeX = XPLMGetDataf(gPlaneX);
- float planeY = XPLMGetDataf(gPlaneY);
- float planeZ = XPLMGetDataf(gPlaneZ);
- float planeHeading = XPLMGetDataf(gPlaneHeading);
- // float planePitch = XPLMGetDataf(gPlanePitch); // unused for now
- float planeRoll = XPLMGetDataf(gPlaneRoll);
-
- // Convert to radians
- float headingRad = planeHeading * M_PI / 180.0f;
- // float pitchRad = planePitch * M_PI / 180.0f; // unused
- float rollRad = planeRoll * M_PI / 180.0f;
- // float panRad = gCameraPan * M_PI / 180.0f; // unused
- // float tiltRad = gCameraTilt * M_PI / 180.0f; // unused
-
- // Calculate camera position relative to aircraft
- // Position camera under the aircraft (belly-mounted)
- float localX = gCameraDistance * cosf(headingRad);
- float localZ = gCameraDistance * sinf(headingRad);
- float localY = gCameraHeight; // Below aircraft
-
- // Apply aircraft roll and pitch to camera position
- float rotatedX = localX * cosf(rollRad) - localY * sinf(rollRad);
- float rotatedY = localX * sinf(rollRad) + localY * cosf(rollRad);
- float rotatedZ = localZ;
-
- // Final camera position in world coordinates
- outCameraPosition->x = planeX + rotatedX;
- outCameraPosition->y = planeY + rotatedY;
- outCameraPosition->z = planeZ + rotatedZ;
-
- // Camera orientation - if locked, track target; otherwise manual control
- if (gTargetLocked && gLockedTargetIndex >= 0 && gLockedTargetIndex < gHeatSourceCount) {
- // Update target position from heat sources
- gTargetX = gHeatSources[gLockedTargetIndex].x;
- gTargetY = gHeatSources[gLockedTargetIndex].y;
- gTargetZ = gHeatSources[gLockedTargetIndex].z;
-
- // Calculate angle to target
- float dx = gTargetX - outCameraPosition->x;
- float dy = gTargetY - outCameraPosition->y;
- float dz = gTargetZ - outCameraPosition->z;
-
- // Calculate heading and pitch to target
- float targetHeading = atan2f(dz, dx) * 180.0f / M_PI;
- float groundDistance = sqrtf(dx*dx + dz*dz);
- float targetPitch = -atan2f(dy, groundDistance) * 180.0f / M_PI;
-
- // Track the target automatically
- outCameraPosition->heading = targetHeading;
- outCameraPosition->pitch = targetPitch;
- } else {
- // Manual camera control
- outCameraPosition->heading = planeHeading + gCameraPan;
- outCameraPosition->pitch = gCameraTilt;
- }
- outCameraPosition->roll = 0.0f; // Keep camera level
-
- // Apply zoom - this is the key for real magnification!
- outCameraPosition->zoom = gZoomLevel;
-
- // Normalize heading
- if (outCameraPosition->heading > 360.0f) outCameraPosition->heading -= 360.0f;
- if (outCameraPosition->heading < 0.0f) outCameraPosition->heading += 360.0f;
- }
-
if (inIsLosingControl) {
+ // We're losing control, clean up
gCameraActive = 0;
- XPLMDebugString("FLIR Camera System: Lost camera control\n");
+ if (gDrawCallbackRegistered) {
+ XPLMUnregisterDrawCallback(DrawThermalOverlay, xplm_Phase_Window, 0, NULL);
+ gDrawCallbackRegistered = 0;
+ }
+ // Re-enable manipulator
+ if (gManipulatorDisabled) {
+ XPLMSetDatai(gManipulatorDisabled, 0);
+ }
+ DisableLockOn();
+ return 0;
}
-
- return gCameraActive;
- }
-
- /*
- * DrawThermalOverlay
- *
- * Draws thermal/IR overlay effects on the camera view
- */
- static int DrawThermalOverlay(XPLMDrawingPhase inPhase, int inIsBefore, void* inRefcon)
- {
- if (!gCameraActive) return 1;
-
- // Debug output to verify callback is being called
- static int callCount = 0;
- if (callCount < 5) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR Camera System: DrawThermalOverlay called, thermal mode: %d\n", gThermalMode);
- XPLMDebugString(debugMsg);
- callCount++;
- }
-
- // Update environmental factors and heat sources
- UpdateEnvironmentalFactors();
- UpdateHeatSources();
- DetectAircraft();
-
- // Set up OpenGL for 2D drawing with proper error checking
- // Get screen dimensions
- int screenWidth, screenHeight;
- XPLMGetScreenSize(&screenWidth, &screenHeight);
-
- if (screenWidth <= 0 || screenHeight <= 0) {
- return 1; // Avoid drawing with invalid screen dimensions
- }
-
- // Save OpenGL state to prevent crashes
- glPushAttrib(GL_ALL_ATTRIB_BITS);
-
- // Set up 2D rendering state
- XPLMSetGraphicsState(0, 0, 0, 1, 1, 0, 0);
- // Window phase provides proper 2D setup - no manual matrix setup needed
-
- // For Window phase, coordinate system is already set up by X-Plane
-
- float centerX = screenWidth / 2.0f;
- float centerY = screenHeight / 2.0f;
+ if (!gPlaneX || !gPlaneY || !gPlaneZ || !gPlaneHeading || !gPlanePitch || !gPlaneRoll) {
+ return 1; // Keep control but don't update position
+ }
- // Mouse control for camera movement
- if (gCameraActive && !gTargetLocked) { // Only when not locked
+ // Get current aircraft position and orientation
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeY = XPLMGetDataf(gPlaneY);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+ float planeHeading = XPLMGetDataf(gPlaneHeading);
+ float planePitch = XPLMGetDataf(gPlanePitch);
+ float planeRoll = XPLMGetDataf(gPlaneRoll);
+
+ // Mouse control for camera movement (only when not locked)
+ if (!IsLockOnActive()) {
int mouseX, mouseY;
XPLMGetMouseLocation(&mouseX, &mouseY);
- static int mouseInitialized = 0;
- if (!mouseInitialized) {
- gLastMouseX = mouseX;
- gLastMouseY = mouseY;
- mouseInitialized = 1;
- }
-
- // Calculate mouse delta
- int deltaX = mouseX - gLastMouseX;
- int deltaY = mouseY - gLastMouseY;
-
- // Apply mouse movement to camera (much more sensitive)
- if (abs(deltaX) > 1 || abs(deltaY) > 1) { // Only if significant movement
- gCameraPan += deltaX * gMouseSensitivity;
- gCameraTilt += deltaY * gMouseSensitivity;
+ if (gLastMouseX != 0 || gLastMouseY != 0) {
+ float deltaX = (mouseX - gLastMouseX) * gMouseSensitivity;
+ float deltaY = (mouseY - gLastMouseY) * gMouseSensitivity;
- // Clamp values
- if (gCameraPan > 180.0f) gCameraPan -= 360.0f;
- if (gCameraPan < -180.0f) gCameraPan += 360.0f;
- gCameraTilt = fmaxf(-90.0f, fminf(45.0f, gCameraTilt));
+ gCameraPan += deltaX;
+ gCameraTilt -= deltaY; // Invert Y for natural control
- gLastMouseX = mouseX;
- gLastMouseY = mouseY;
- }
- }
-
- // Draw targeting reticle
- if (callCount < 3) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Drawing crosshair at %.0f,%.0f (screen %dx%d)\n", centerX, centerY, screenWidth, screenHeight);
- XPLMDebugString(debugMsg);
- }
-
- glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
- glLineWidth(3.0f);
-
- glBegin(GL_LINES);
- // No crosshair - only brackets
-
- // Military-style targeting brackets [ ] - FIXED SIZE
- float bracketSize = 50.0f; // Fixed size - doesn't change with zoom
- float bracketLength = 20.0f;
-
- // Top-left bracket [
- glVertex2f(centerX - bracketSize, centerY + bracketSize);
- glVertex2f(centerX - bracketSize + bracketLength, centerY + bracketSize);
- glVertex2f(centerX - bracketSize, centerY + bracketSize);
- glVertex2f(centerX - bracketSize, centerY + bracketSize - bracketLength);
-
- // Top-right bracket ]
- glVertex2f(centerX + bracketSize, centerY + bracketSize);
- glVertex2f(centerX + bracketSize - bracketLength, centerY + bracketSize);
- glVertex2f(centerX + bracketSize, centerY + bracketSize);
- glVertex2f(centerX + bracketSize, centerY + bracketSize - bracketLength);
-
- // Bottom-left bracket [
- glVertex2f(centerX - bracketSize, centerY - bracketSize);
- glVertex2f(centerX - bracketSize + bracketLength, centerY - bracketSize);
- glVertex2f(centerX - bracketSize, centerY - bracketSize);
- glVertex2f(centerX - bracketSize, centerY - bracketSize + bracketLength);
-
- // Bottom-right bracket ]
- glVertex2f(centerX + bracketSize, centerY - bracketSize);
- glVertex2f(centerX + bracketSize - bracketLength, centerY - bracketSize);
- glVertex2f(centerX + bracketSize, centerY - bracketSize);
- glVertex2f(centerX + bracketSize, centerY - bracketSize + bracketLength);
-
- // Small center dot for precise aiming
- glVertex2f(centerX - 2, centerY);
- glVertex2f(centerX + 2, centerY);
- glVertex2f(centerX, centerY - 2);
- glVertex2f(centerX, centerY + 2);
- glEnd();
-
- // Draw thermal effects based on thermal mode - using same reliable approach as crosshair
- if (gThermalMode > 0) {
- if (callCount < 3) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Drawing thermal overlay mode %d\n", gThermalMode);
- XPLMDebugString(debugMsg);
- }
-
- // Full-screen thermal overlay using same drawing method as crosshair
- glEnable(GL_BLEND);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-
- // Draw thermal background overlay
- if (gThermalMode == 1) {
- // White Hot mode - dark blue/black background
- glColor4f(0.05f, 0.1f, 0.2f, 0.7f); // More opaque for realistic IR view
- } else {
- // Enhanced mode - greenish IR background
- glColor4f(0.1f, 0.3f, 0.1f, 0.6f);
- }
-
- glBegin(GL_QUADS);
- glVertex2f(0, 0);
- glVertex2f(screenWidth, 0);
- glVertex2f(screenWidth, screenHeight);
- glVertex2f(0, screenHeight);
- glEnd();
-
- // Realistic IR scan lines only (no random noise)
- glLineWidth(1.0f);
- glColor4f(0.0f, 0.0f, 0.0f, 0.15f);
-
- // Horizontal scan lines like real IR cameras
- for (int y = 0; y < screenHeight; y += 2) {
- glBegin(GL_LINES);
- glVertex2f(0, y);
- glVertex2f(screenWidth, y);
- glEnd();
+ // Normalize pan angle
+ while (gCameraPan > 180.0f) gCameraPan -= 360.0f;
+ while (gCameraPan < -180.0f) gCameraPan += 360.0f;
+
+ // Clamp tilt
+ if (gCameraTilt > 45.0f) gCameraTilt = 45.0f;
+ if (gCameraTilt < -90.0f) gCameraTilt = -90.0f;
}
- // Only draw realistic heat sources from actual detection, no fake test rectangles
-
- // Draw realistic heat sources if we have any
- if (gHeatSourceCount > 0) {
- DrawRealisticThermalOverlay();
- }
-
- // No background thermal noise - cleaner IR view
-
- glDisable(GL_BLEND);
- }
-
- // Draw realistic military FLIR HUD overlay
- glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
- glLineWidth(1.0f);
-
- // Minimal corner reticles only (like real FLIR systems)
- float reticleSize = 15.0f;
- glBegin(GL_LINES);
- // Top-left reticle
- glVertex2f(30, 30); glVertex2f(30 + reticleSize, 30);
- glVertex2f(30, 30); glVertex2f(30, 30 + reticleSize);
- // Top-right reticle
- glVertex2f(screenWidth - 30 - reticleSize, 30); glVertex2f(screenWidth - 30, 30);
- glVertex2f(screenWidth - 30, 30); glVertex2f(screenWidth - 30, 30 + reticleSize);
- // Bottom-left reticle
- glVertex2f(30, screenHeight - 30 - reticleSize); glVertex2f(30, screenHeight - 30);
- glVertex2f(30, screenHeight - 30); glVertex2f(30 + reticleSize, screenHeight - 30);
- // Bottom-right reticle
- glVertex2f(screenWidth - 30 - reticleSize, screenHeight - 30); glVertex2f(screenWidth - 30, screenHeight - 30);
- glVertex2f(screenWidth - 30, screenHeight - 30); glVertex2f(screenWidth - 30, screenHeight - 30 - reticleSize);
- glEnd();
-
- // Data will be exported to shared datarefs for FlyWithLua display
- // For now, minimal built-in display only
-
- // Use X-Plane's text rendering for professional look
- glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
-
- // Minimal status indicators (no confusing abstract lines)
- // Only show essential info
-
- // Zoom level indicator (bottom-left corner only)
- glLineWidth(2.0f);
- glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
- glBegin(GL_LINES);
- float zoomBar = fminf(gZoomLevel * 5.0f, 100.0f); // Scale zoom to reasonable bar length
- glVertex2f(30, 40);
- glVertex2f(30 + zoomBar, 40);
- glEnd();
-
- // TARGET LOCK INDICATOR - VERY VISIBLE
- if (gTargetLocked) {
- glColor4f(1.0f, 0.0f, 0.0f, 1.0f); // Bright red for locked
- glLineWidth(4.0f);
-
- // Large red box around the entire targeting area
- glBegin(GL_LINE_LOOP);
- glVertex2f(centerX - 100, centerY - 100);
- glVertex2f(centerX + 100, centerY - 100);
- glVertex2f(centerX + 100, centerY + 100);
- glVertex2f(centerX - 100, centerY + 100);
- glEnd();
-
- // Flashing corner indicators
- float time = XPLMGetElapsedTime();
- float flash = (sinf(time * 4.0f) + 1.0f) * 0.5f; // 0-1 flashing
- glColor4f(1.0f, 0.0f, 0.0f, 0.5f + flash * 0.5f);
-
- glBegin(GL_LINES);
- float lockSize = 30.0f;
- // Top-left corner
- glVertex2f(centerX - 100, centerY + 100 - lockSize); glVertex2f(centerX - 100, centerY + 100);
- glVertex2f(centerX - 100, centerY + 100); glVertex2f(centerX - 100 + lockSize, centerY + 100);
- // Top-right corner
- glVertex2f(centerX + 100 - lockSize, centerY + 100); glVertex2f(centerX + 100, centerY + 100);
- glVertex2f(centerX + 100, centerY + 100); glVertex2f(centerX + 100, centerY + 100 - lockSize);
- // Bottom-left corner
- glVertex2f(centerX - 100, centerY - 100 + lockSize); glVertex2f(centerX - 100, centerY - 100);
- glVertex2f(centerX - 100, centerY - 100); glVertex2f(centerX - 100 + lockSize, centerY - 100);
- // Bottom-right corner
- glVertex2f(centerX + 100 - lockSize, centerY - 100); glVertex2f(centerX + 100, centerY - 100);
- glVertex2f(centerX + 100, centerY - 100); glVertex2f(centerX + 100, centerY - 100 + lockSize);
- glEnd();
-
- // LOCKED text indicator (top of screen)
- glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
- glLineWidth(3.0f);
- glBegin(GL_LINES);
- // Simple "LOCKED" text using lines at top center
- float textY = screenHeight - 100;
- // L
- glVertex2f(centerX - 80, textY); glVertex2f(centerX - 80, textY - 20);
- glVertex2f(centerX - 80, textY - 20); glVertex2f(centerX - 65, textY - 20);
- // O
- glVertex2f(centerX - 60, textY); glVertex2f(centerX - 45, textY);
- glVertex2f(centerX - 60, textY - 20); glVertex2f(centerX - 45, textY - 20);
- glVertex2f(centerX - 60, textY); glVertex2f(centerX - 60, textY - 20);
- glVertex2f(centerX - 45, textY); glVertex2f(centerX - 45, textY - 20);
- // C
- glVertex2f(centerX - 40, textY); glVertex2f(centerX - 25, textY);
- glVertex2f(centerX - 40, textY - 20); glVertex2f(centerX - 25, textY - 20);
- glVertex2f(centerX - 40, textY); glVertex2f(centerX - 40, textY - 20);
- // K
- glVertex2f(centerX - 20, textY); glVertex2f(centerX - 20, textY - 20);
- glVertex2f(centerX - 20, textY - 10); glVertex2f(centerX - 5, textY);
- glVertex2f(centerX - 20, textY - 10); glVertex2f(centerX - 5, textY - 20);
- // E
- glVertex2f(centerX, textY); glVertex2f(centerX, textY - 20);
- glVertex2f(centerX, textY); glVertex2f(centerX + 15, textY);
- glVertex2f(centerX, textY - 10); glVertex2f(centerX + 10, textY - 10);
- glVertex2f(centerX, textY - 20); glVertex2f(centerX + 15, textY - 20);
- // D
- glVertex2f(centerX + 20, textY); glVertex2f(centerX + 20, textY - 20);
- glVertex2f(centerX + 20, textY); glVertex2f(centerX + 30, textY - 5);
- glVertex2f(centerX + 30, textY - 5); glVertex2f(centerX + 30, textY - 15);
- glVertex2f(centerX + 30, textY - 15); glVertex2f(centerX + 20, textY - 20);
- glEnd();
+ gLastMouseX = mouseX;
+ gLastMouseY = mouseY;
+ } else {
+ // Update camera angles based on lock-on point
+ UpdateCameraToLockPoint(&gCameraPan, &gCameraTilt);
}
- // No range circles - clean display
+ // Calculate camera position (belly-mounted)
+ float headingRad = planeHeading * M_PI / 180.0f;
+ float pitchRad = planePitch * M_PI / 180.0f;
+ float rollRad = planeRoll * M_PI / 180.0f;
- // Heading indicator (top center)
- glColor4f(0.0f, 1.0f, 0.0f, 0.9f);
- glLineWidth(2.0f);
- float headingX = centerX;
- float headingY = screenHeight - 40;
- float currentHeading = XPLMGetDataf(gPlaneHeading);
+ // Position camera below and slightly forward of aircraft center
+ outCameraPosition->x = planeX + gCameraDistance * sin(headingRad);
+ outCameraPosition->y = planeY + gCameraHeight;
+ outCameraPosition->z = planeZ + gCameraDistance * cos(headingRad);
- glBegin(GL_LINES);
- // Heading scale
- for (int h = 0; h < 360; h += 30) {
- float angle = (h - currentHeading - gCameraPan) * M_PI / 180.0f;
- float x1 = headingX + 100 * sinf(angle);
- float x2 = headingX + 110 * sinf(angle);
- if (x1 > 50 && x1 < screenWidth - 50) {
- glVertex2f(x1, headingY);
- glVertex2f(x2, headingY);
- }
- }
- glEnd();
+ // Camera orientation (pan/tilt relative to aircraft heading)
+ outCameraPosition->heading = planeHeading + gCameraPan;
+ outCameraPosition->pitch = gCameraTilt;
+ outCameraPosition->roll = 0.0f; // Keep camera level
- // Center heading marker
- glBegin(GL_LINES);
- glVertex2f(headingX, headingY - 10);
- glVertex2f(headingX, headingY + 10);
- glEnd();
+ // Apply zoom
+ outCameraPosition->zoom = gZoomLevel;
- // Restore OpenGL state to prevent crashes
- glPopAttrib();
-
- return 1;
- }
-
- /*
- * Additional utility functions for future enhancements
- */
-
- // Function to detect and track actual X-Plane objects
- /*
-static void UpdateTargetTracking(void)
- {
- // Future enhancement: Use X-Plane's object detection APIs
- // to find and track actual aircraft, ships, vehicles in the sim
+ return 1; // Keep controlling camera
}
- // Function to simulate different thermal/IR modes
- static void SetThermalMode(int mode)
+ // Drawing callback for thermal overlay
+ static int DrawThermalOverlay(XPLMDrawingPhase inPhase,
+ int inIsBefore,
+ void* inRefcon)
{
- // Future enhancement: Different thermal rendering modes
- // 0 = White Hot, 1 = Black Hot, 2 = Rainbow, etc.
+ if (!gCameraActive) return 1;
+
+ DrawRealisticThermalOverlay();
+
+ return 1;
}
-*/
-/*
- * Enhanced Functions for FLIR Camera Plugin
- * These functions provide realistic thermal detection and environmental factors
- */
-
-/*
- * UpdateEnvironmentalFactors
- *
- * Updates environmental conditions that affect thermal visibility
- */
-static void UpdateEnvironmentalFactors(void)
-{
- if (gWeatherVisibility != NULL) {
- gCurrentVisibility = XPLMGetDataf(gWeatherVisibility);
- }
-
- if (gAmbientTemperature != NULL) {
- gCurrentTemp = XPLMGetDataf(gAmbientTemperature);
- }
-
- if (gWindSpeed != NULL) {
- gCurrentWindSpeed = XPLMGetDataf(gWindSpeed);
- }
-
- if (gTimeOfDay != NULL) {
- float timeOfDay = XPLMGetDataf(gTimeOfDay);
- // Night is roughly 18:00 to 06:00 (18*3600 = 64800, 6*3600 = 21600)
- gIsNight = (timeOfDay > 64800.0f || timeOfDay < 21600.0f) ? 1 : 0;
- }
-}
-
-/*
- * GetDistanceToCamera
- *
- * Calculate distance from world position to camera
- */
-static float GetDistanceToCamera(float x, float y, float z)
-{
- float camX = XPLMGetDataf(gPlaneX);
- float camY = XPLMGetDataf(gPlaneY);
- float camZ = XPLMGetDataf(gPlaneZ);
-
- float dx = x - camX;
- float dy = y - camY;
- float dz = z - camZ;
-
- return sqrtf(dx*dx + dy*dy + dz*dz);
-}
-
-/*
- * CalculateHeatIntensity
- *
- * Calculate thermal intensity based on engine temperature and distance
- */
-static float CalculateHeatIntensity(float engineTemp, float distance, int engineRunning)
-{
- if (!engineRunning) return 0.1f; // Cold engine still slightly visible
-
- // Base intensity from engine temperature (EGT typically 400-900°C)
- float baseIntensity = (engineTemp - 200.0f) / 700.0f;
- baseIntensity = fmaxf(0.0f, fminf(1.0f, baseIntensity));
-
- // Distance attenuation (max detection ~10km for aircraft)
- float distanceFactor = 1.0f - (distance / 10000.0f);
- distanceFactor = fmaxf(0.0f, distanceFactor);
-
- // Weather effects
- float weatherFactor = gCurrentVisibility / 10000.0f;
- weatherFactor = fmaxf(0.2f, fminf(1.0f, weatherFactor));
-
- // Night enhances thermal contrast
- float nightBonus = gIsNight ? 1.3f : 1.0f;
-
- return baseIntensity * distanceFactor * weatherFactor * nightBonus;
-}
-
-/*
- * DetectAircraft
- *
- * Scan for AI aircraft and other heat sources in the simulation
- */
-static void DetectAircraft(void)
-{
- gAircraftCount = 0;
-
- // Debug output for aircraft detection
- static int detectCallCount = 0;
- detectCallCount++;
- if (detectCallCount % 300 == 1) { // Every ~5 seconds
- XPLMDebugString("FLIR Camera System: Scanning for AI aircraft and ships...\n");
- }
-
- // **1. Detect AI Aircraft (much more common than multiplayer)**
- // X-Plane has up to 63 AI aircraft slots
- for (int i = 0; i < 63 && gAircraftCount < 20; i++) {
- char datarefName[256];
-
- // AI aircraft positions
- sprintf(datarefName, "sim/multiplayer/position/plane%d_x", i + 1);
- XPLMDataRef aircraftX = XPLMFindDataRef(datarefName);
- sprintf(datarefName, "sim/multiplayer/position/plane%d_y", i + 1);
- XPLMDataRef aircraftY = XPLMFindDataRef(datarefName);
- sprintf(datarefName, "sim/multiplayer/position/plane%d_z", i + 1);
- XPLMDataRef aircraftZ = XPLMFindDataRef(datarefName);
-
- if (aircraftX && aircraftY && aircraftZ) {
- float x = XPLMGetDataf(aircraftX);
- float y = XPLMGetDataf(aircraftY);
- float z = XPLMGetDataf(aircraftZ);
-
- // Check if aircraft exists and is not our own plane
- if ((x != 0.0f || y != 0.0f || z != 0.0f)) {
- float distance = GetDistanceToCamera(x, y, z);
- if (distance > 50.0f) { // Minimum distance to avoid locking on self
- gAircraftPositions[gAircraftCount][0] = x;
- gAircraftPositions[gAircraftCount][1] = y;
- gAircraftPositions[gAircraftCount][2] = z;
- gAircraftDistance[gAircraftCount] = distance;
-
- // Simulate engine temperature (realistic EGT values)
- gAircraftEngineTemp[gAircraftCount] = 450.0f + (rand() % 300);
-
- // Determine visibility based on distance and conditions
- gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility * 0.8f) ? 1 : 0;
-
- if (detectCallCount % 300 == 1) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Found AI aircraft %d at %.0fm\n", i+1, distance);
- XPLMDebugString(debugMsg);
- }
-
- gAircraftCount++;
- }
- }
- }
- }
-
- // **2. Detect Ships - try multiple ship/boat datarefs**
-
- // Method 1: Try ground vehicle datarefs that might be ships
- XPLMDataRef shipX = XPLMFindDataRef("sim/flightmodel2/position/local_x");
- XPLMDataRef shipY = XPLMFindDataRef("sim/flightmodel2/position/local_y");
- XPLMDataRef shipZ = XPLMFindDataRef("sim/flightmodel2/position/local_z");
-
- if (shipX && shipY && shipZ && gAircraftCount < 20) {
- float x = XPLMGetDataf(shipX);
- float y = XPLMGetDataf(shipY);
- float z = XPLMGetDataf(shipZ);
-
- if (x != 0.0f || y != 0.0f || z != 0.0f) {
- float distance = GetDistanceToCamera(x, y, z);
- if (distance > 50.0f) {
- gAircraftPositions[gAircraftCount][0] = x;
- gAircraftPositions[gAircraftCount][1] = y;
- gAircraftPositions[gAircraftCount][2] = z;
- gAircraftDistance[gAircraftCount] = distance;
- gAircraftEngineTemp[gAircraftCount] = 250.0f + (rand() % 150); // Ships have lower heat
- gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility) ? 1 : 0;
-
- if (detectCallCount % 300 == 1) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Found ship/vehicle at %.0fm\n", distance);
- XPLMDebugString(debugMsg);
- }
- gAircraftCount++;
- }
- }
- }
-
- // Method 2: AI boat datarefs
- for (int i = 0; i < 10 && gAircraftCount < 20; i++) {
- char datarefName[256];
- sprintf(datarefName, "sim/multiplayer/position/boat%d_x", i + 1);
- XPLMDataRef boatX = XPLMFindDataRef(datarefName);
- sprintf(datarefName, "sim/multiplayer/position/boat%d_y", i + 1);
- XPLMDataRef boatY = XPLMFindDataRef(datarefName);
- sprintf(datarefName, "sim/multiplayer/position/boat%d_z", i + 1);
- XPLMDataRef boatZ = XPLMFindDataRef(datarefName);
-
- if (boatX && boatY && boatZ) {
- float x = XPLMGetDataf(boatX);
- float y = XPLMGetDataf(boatY);
- float z = XPLMGetDataf(boatZ);
-
- if (x != 0.0f || y != 0.0f || z != 0.0f) {
- float distance = GetDistanceToCamera(x, y, z);
- if (distance > 50.0f) {
- gAircraftPositions[gAircraftCount][0] = x;
- gAircraftPositions[gAircraftCount][1] = y;
- gAircraftPositions[gAircraftCount][2] = z;
- gAircraftDistance[gAircraftCount] = distance;
-
- // Ships have engine heat too
- gAircraftEngineTemp[gAircraftCount] = 300.0f + (rand() % 200);
- gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility) ? 1 : 0;
-
- if (detectCallCount % 300 == 1) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Found ship %d at %.0fm\n", i+1, distance);
- XPLMDebugString(debugMsg);
- }
-
- gAircraftCount++;
- }
- }
- }
- }
-
- if (detectCallCount % 300 == 1) {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR: Total targets found: %d\n", gAircraftCount);
- XPLMDebugString(debugMsg);
- }
-}
-/*
- * UpdateHeatSources
- *
- * Update dynamic heat sources in the simulation
- */
-static void UpdateHeatSources(void)
-{
- float currentTime = XPLMGetElapsedTime();
- gHeatSourceCount = 0;
-
- // Add detected aircraft as heat sources
- for (int i = 0; i < gAircraftCount && gHeatSourceCount < 50; i++) {
- if (gAircraftVisible[i]) {
- gHeatSources[gHeatSourceCount].x = gAircraftPositions[i][0];
- gHeatSources[gHeatSourceCount].y = gAircraftPositions[i][1];
- gHeatSources[gHeatSourceCount].z = gAircraftPositions[i][2];
- gHeatSources[gHeatSourceCount].intensity = CalculateHeatIntensity(
- gAircraftEngineTemp[i], gAircraftDistance[i], 1);
- gHeatSources[gHeatSourceCount].size = 15.0f + (gHeatSources[gHeatSourceCount].intensity * 25.0f);
- gHeatSources[gHeatSourceCount].type = 0; // Aircraft
- gHeatSources[gHeatSourceCount].lastUpdate = currentTime;
- gHeatSourceCount++;
- }
- }
-
- // Add simulated ground heat sources (buildings, vehicles, ships)
- float planeX = XPLMGetDataf(gPlaneX);
- float planeZ = XPLMGetDataf(gPlaneZ);
-
- // Simulate some heat sources around the area
- for (int i = 0; i < 8 && gHeatSourceCount < 50; i++) {
- float angle = (i * 45.0f) * M_PI / 180.0f;
- float distance = 2000.0f + (i * 500.0f);
-
- float x = planeX + distance * cosf(angle);
- float z = planeZ + distance * sinf(angle);
- float y = XPLMGetDataf(gPlaneY) - 100.0f; // Ground level
-
- gHeatSources[gHeatSourceCount].x = x;
- gHeatSources[gHeatSourceCount].y = y;
- gHeatSources[gHeatSourceCount].z = z;
-
- // Vary intensity based on type and time
- float baseIntensity = 0.3f;
- if (gIsNight) baseIntensity += 0.2f; // More visible at night
-
- gHeatSources[gHeatSourceCount].intensity = baseIntensity +
- 0.2f * sinf(currentTime * 0.1f + i);
- gHeatSources[gHeatSourceCount].size = 8.0f + (gHeatSources[gHeatSourceCount].intensity * 12.0f);
- gHeatSources[gHeatSourceCount].type = (i % 3) + 1; // Ships, vehicles, buildings
- gHeatSources[gHeatSourceCount].lastUpdate = currentTime;
- gHeatSourceCount++;
- }
-}
-
-/*
- * DrawRealisticThermalOverlay
- *
- * Render realistic thermal signatures for detected heat sources
- */
+// Realistic thermal overlay drawing
static void DrawRealisticThermalOverlay(void)
{
- if (gHeatSourceCount == 0) return;
-
- // Get screen dimensions and camera info
int screenWidth, screenHeight;
XPLMGetScreenSize(&screenWidth, &screenHeight);
- float camX = XPLMGetDataf(gPlaneX);
- float camY = XPLMGetDataf(gPlaneY);
- float camZ = XPLMGetDataf(gPlaneZ);
- float camHeading = XPLMGetDataf(gPlaneHeading) + gCameraPan;
- float camPitch = gCameraTilt;
-
- // Convert camera angles to radians
- float headingRad = camHeading * M_PI / 180.0f;
- float pitchRad = camPitch * M_PI / 180.0f;
-
- // Camera forward vector
- float forwardX = cosf(headingRad) * cosf(pitchRad);
- float forwardY = sinf(pitchRad);
- float forwardZ = sinf(headingRad) * cosf(pitchRad);
+ // Set up OpenGL for 2D drawing
+ glMatrixMode(GL_PROJECTION);
+ glPushMatrix();
+ glLoadIdentity();
+ glOrtho(0, screenWidth, screenHeight, 0, -1, 1);
- // Camera right vector (for screen mapping)
- float rightX = -sinf(headingRad);
- float rightZ = cosf(headingRad);
-
- // Camera up vector
- float upX = -cosf(headingRad) * sinf(pitchRad);
- float upY = cosf(pitchRad);
- float upZ = -sinf(headingRad) * sinf(pitchRad);
-
- // Field of view factor (approximate)
- float fovFactor = 60.0f / gZoomLevel; // Base FOV 60 degrees
+ glMatrixMode(GL_MODELVIEW);
+ glPushMatrix();
+ glLoadIdentity();
+ // Disable depth testing for overlay
+ glDisable(GL_DEPTH_TEST);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
- // Draw each heat source
- for (int i = 0; i < gHeatSourceCount; i++) {
- HeatSource* heat = &gHeatSources[i];
-
- // Vector from camera to heat source
- float dx = heat->x - camX;
- float dy = heat->y - camY;
- float dz = heat->z - camZ;
- float distance = sqrtf(dx*dx + dy*dy + dz*dz);
-
- if (distance < 50.0f) continue; // Too close, skip
-
- // Normalize direction vector
- dx /= distance;
- dy /= distance;
- dz /= distance;
-
- // Check if heat source is in front of camera
- float dotForward = dx * forwardX + dy * forwardY + dz * forwardZ;
- if (dotForward <= 0.1f) continue; // Behind camera or too far to side
-
- // Project to screen coordinates
- float rightDot = dx * rightX + dz * rightZ;
- float upDot = dx * upX + dy * upY + dz * upZ;
-
- // Convert to screen space
- float screenX = screenWidth * 0.5f + (rightDot * screenWidth * 0.5f / fovFactor);
- float screenY = screenHeight * 0.5f + (upDot * screenHeight * 0.5f / fovFactor);
-
- // Check if on screen
- if (screenX < 0 || screenX >= screenWidth || screenY < 0 || screenY >= screenHeight) {
- continue;
- }
-
- // Calculate apparent size based on distance and zoom
- float apparentSize = heat->size * gZoomLevel / (distance * 0.01f);
- apparentSize = fmaxf(3.0f, fminf(apparentSize, 50.0f));
-
- // Color based on heat source type and intensity
- float r, g, b, a;
- if (gThermalMode == 1) {
- // White hot mode
- r = g = b = heat->intensity;
- a = heat->intensity * 0.8f;
-
- // Aircraft show as very bright white
- if (heat->type == 0) {
- r = g = b = 1.0f;
- a = 0.9f;
- }
- } else {
- // Enhanced mode - use color coding
- switch (heat->type) {
- case 0: // Aircraft - bright yellow/orange
- r = 1.0f;
- g = 0.8f;
- b = 0.2f;
- a = heat->intensity * 0.9f;
- break;
- case 1: // Ships - cyan
- r = 0.2f;
- g = 0.8f;
- b = 1.0f;
- a = heat->intensity * 0.7f;
- break;
- case 2: // Vehicles - red
- r = 1.0f;
- g = 0.3f;
- b = 0.1f;
- a = heat->intensity * 0.6f;
- break;
- case 3: // Buildings - orange
- r = 1.0f;
- g = 0.5f;
- b = 0.0f;
- a = heat->intensity * 0.5f;
- break;
- default:
- r = g = b = heat->intensity;
- a = heat->intensity * 0.6f;
- break;
- }
- }
-
- glColor4f(r, g, b, a);
-
- // Draw heat signature with gradient effect
+ // Draw thermal border effect if thermal mode is active
+ if (gThermalMode > 0) {
+ glColor4f(0.0f, 1.0f, 0.0f, 0.3f); // Subtle green tint
glBegin(GL_QUADS);
- glVertex2f(screenX - apparentSize, screenY - apparentSize);
- glVertex2f(screenX + apparentSize, screenY - apparentSize);
- glVertex2f(screenX + apparentSize, screenY + apparentSize);
- glVertex2f(screenX - apparentSize, screenY + apparentSize);
+ // Top border
+ glVertex2f(0, 0);
+ glVertex2f(screenWidth, 0);
+ glVertex2f(screenWidth, 20);
+ glVertex2f(0, 20);
+ // Bottom border
+ glVertex2f(0, screenHeight - 20);
+ glVertex2f(screenWidth, screenHeight - 20);
+ glVertex2f(screenWidth, screenHeight);
+ glVertex2f(0, screenHeight);
glEnd();
-
- // Add heat bloom effect for intense sources
- if (heat->intensity > 0.7f) {
- float bloomSize = apparentSize * 1.5f;
- glColor4f(r, g, b, a * 0.3f);
- glBegin(GL_QUADS);
- glVertex2f(screenX - bloomSize, screenY - bloomSize);
- glVertex2f(screenX + bloomSize, screenY - bloomSize);
- glVertex2f(screenX + bloomSize, screenY + bloomSize);
- glVertex2f(screenX - bloomSize, screenY + bloomSize);
- glEnd();
- }
-
- // Add targeting indicator for aircraft
- if (heat->type == 0 && distance < 5000.0f) {
- glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
- glLineWidth(2.0f);
-
- float targetSize = apparentSize + 10.0f;
- glBegin(GL_LINE_LOOP);
- glVertex2f(screenX - targetSize, screenY - targetSize);
- glVertex2f(screenX + targetSize, screenY - targetSize);
- glVertex2f(screenX + targetSize, screenY + targetSize);
- glVertex2f(screenX - targetSize, screenY + targetSize);
- glEnd();
-
- // Add distance indicator
- char distText[32];
- sprintf(distText, "%.0fm", distance);
- // Note: Actual text rendering would need XPLMDrawString
- }
}
- glDisable(GL_BLEND);
-}
-
-static void FocusLockCallback(void* inRefcon)
-{
- if (gCameraActive) {
- if (!gTargetLocked) {
- // Find nearest target to focus on
- float nearestDistance = 999999.0f;
- int nearestTarget = -1;
-
- // Check heat sources for nearest aircraft
- for (int i = 0; i < gHeatSourceCount; i++) {
- if (gHeatSources[i].type == 0) { // Aircraft
- float distance = GetDistanceToCamera(gHeatSources[i].x, gHeatSources[i].y, gHeatSources[i].z);
- if (distance < nearestDistance && distance > 100.0f) { // Minimum 100m distance
- nearestDistance = distance;
- nearestTarget = i;
- }
- }
- }
-
- if (nearestTarget >= 0) {
- gTargetLocked = 1;
- gLockedTargetIndex = nearestTarget;
- gFocusDistance = nearestDistance;
- gTargetX = gHeatSources[nearestTarget].x;
- gTargetY = gHeatSources[nearestTarget].y;
- gTargetZ = gHeatSources[nearestTarget].z;
- char msg[256];
- sprintf(msg, "FLIR Camera System: Target LOCKED at %.0fm - Camera will track automatically\n", gFocusDistance);
- XPLMDebugString(msg);
- } else {
- char debugMsg[256];
- sprintf(debugMsg, "FLIR Camera System: No targets found - Heat sources: %d, Aircraft: %d\n",
- gHeatSourceCount, gAircraftCount);
- XPLMDebugString(debugMsg);
- }
- } else {
- // Unlock target
- gTargetLocked = 0;
- gLockedTargetIndex = -1;
- XPLMDebugString("FLIR Camera System: Target UNLOCKED - Manual control restored\n");
- }
+ // Military-style targeting brackets [ ] - FIXED SIZE
+ float centerX = screenWidth / 2.0f;
+ float centerY = screenHeight / 2.0f;
+ float bracketSize = 50.0f; // Fixed size - doesn't change with zoom
+ float bracketLength = 20.0f;
+
+ // Set color based on lock status
+ if (IsLockOnActive()) {
+ glColor4f(1.0f, 0.0f, 0.0f, 0.8f); // Red when locked
+ } else {
+ glColor4f(0.0f, 1.0f, 0.0f, 0.8f); // Green when scanning
}
+
+ glLineWidth(2.0f);
+ glBegin(GL_LINES);
+
+ // Top-left bracket [
+ glVertex2f(centerX - bracketSize, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize + bracketLength, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize, centerY - bracketSize + bracketLength);
+
+ // Top-right bracket ]
+ glVertex2f(centerX + bracketSize, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize - bracketLength, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize, centerY - bracketSize + bracketLength);
+
+ // Bottom-left bracket [
+ glVertex2f(centerX - bracketSize, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize + bracketLength, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize, centerY + bracketSize - bracketLength);
+
+ // Bottom-right bracket ]
+ glVertex2f(centerX + bracketSize, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize - bracketLength, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize, centerY + bracketSize - bracketLength);
+
+ glEnd();
+
+ // Center dot
+ glPointSize(4.0f);
+ glBegin(GL_POINTS);
+ glVertex2f(centerX, centerY);
+ glEnd();
+
+ // Restore OpenGL state
+ glEnable(GL_DEPTH_TEST);
+ glDisable(GL_BLEND);
+ glLineWidth(1.0f);
+ glPointSize(1.0f);
+
+ glPopMatrix();
+ glMatrixMode(GL_PROJECTION);
+ glPopMatrix();
+ glMatrixMode(GL_MODELVIEW);
}
\ No newline at end of file
diff --git a/FLIR_Camera.o b/FLIR_Camera.o
index 98a49b4..a527ffd 100644
Binary files a/FLIR_Camera.o and b/FLIR_Camera.o differ
diff --git a/FLIR_Camera_OLD.cpp b/FLIR_Camera_OLD.cpp
new file mode 100644
index 0000000..9e9cca2
--- /dev/null
+++ b/FLIR_Camera_OLD.cpp
@@ -0,0 +1,1308 @@
+/*
+ * FLIR_Camera.cpp
+ *
+ * A realistic FLIR camera system plugin for X-Plane 12
+ * Features:
+ * - Real camera positioning under aircraft (belly-mounted)
+ * - True optical zoom with zoom parameter
+ * - Pan/tilt camera controls
+ * - Target acquisition and tracking
+ * - Thermal overlay rendering
+ *
+ */
+
+ #include <string.h>
+ #include <stdio.h>
+ #include <math.h>
+ #ifndef M_PI
+ #define M_PI 3.14159265358979323846
+ #endif
+ #include "XPLMDisplay.h"
+ #include "XPLMUtilities.h"
+ #include "XPLMCamera.h"
+ #include "XPLMDataAccess.h"
+ #include "XPLMGraphics.h"
+ #include "XPLMProcessing.h"
+ #include "XPLMMenus.h"
+ #include "XPLMScenery.h"
+ #include "XPLMPlanes.h"
+#include "XPLMNavigation.h"
+#include "XPLMInstance.h"
+#include "XPLMWeather.h"
+
+ // OpenGL includes for MinGW
+ #include <windows.h>
+ #include <GL/gl.h>
+
+ // Plugin globals
+ static XPLMHotKeyID gActivateKey = NULL;
+ static XPLMHotKeyID gZoomInKey = NULL;
+ static XPLMHotKeyID gZoomOutKey = NULL;
+ static XPLMHotKeyID gPanLeftKey = NULL;
+ static XPLMHotKeyID gPanRightKey = NULL;
+ static XPLMHotKeyID gTiltUpKey = NULL;
+ static XPLMHotKeyID gTiltDownKey = NULL;
+static XPLMHotKeyID gThermalToggleKey = NULL;
+static XPLMHotKeyID gFocusLockKey = NULL;
+
+ // Aircraft position datarefs
+ static XPLMDataRef gPlaneX = NULL;
+ static XPLMDataRef gPlaneY = NULL;
+ static XPLMDataRef gPlaneZ = NULL;
+ static XPLMDataRef gPlaneHeading = NULL;
+ static XPLMDataRef gPlanePitch = NULL;
+ static XPLMDataRef gPlaneRoll = NULL;
+
+ // FLIR camera state
+ static int gCameraActive = 0;
+static int gDrawCallbackRegistered = 0;
+ static float gZoomLevel = 1.0f;
+ static float gCameraPan = 0.0f; // Left/right rotation (degrees)
+ static float gCameraTilt = -15.0f; // Up/down rotation (degrees)
+ static float gCameraHeight = -5.0f; // Height below aircraft (meters)
+ static float gCameraDistance = 3.0f; // Forward/back from aircraft center
+
+// Mouse control for camera
+static int gLastMouseX = 0;
+static int gLastMouseY = 0;
+static float gMouseSensitivity = 0.2f; // Mouse sensitivity multiplier
+
+// Thermal view settings
+static int gThermalMode = 1; // 0=Off, 1=White Hot, 2=Enhanced
+
+ // Target tracking and object detection
+ static int gTargetLocked = 0;
+static float gFocusDistance = 1000.0f; // Distance to focused target
+ static float gTargetX = 0.0f; // World position of locked target
+ static float gTargetY = 0.0f;
+ static float gTargetZ = 0.0f;
+ static int gLockedTargetIndex = -1; // Index of locked target in heat sources
+
+ // Aircraft tracking arrays (up to 20 aircraft)
+ static float gAircraftPositions[20][3]; // x, y, z positions
+ static float gAircraftEngineTemp[20]; // Engine temperatures
+ static float gAircraftDistance[20]; // Distance from camera
+static int gAircraftVisible[20]; // Visibility in thermal
+static int gAircraftCount = 0;
+
+// Environmental factors
+static float gCurrentVisibility = 10000.0f;
+static float gCurrentTemp = 15.0f;
+static float gCurrentWindSpeed = 0.0f;
+static int gIsNight = 0;
+
+// Heat source simulation
+typedef struct {
+ float x, y, z; // World position
+ float intensity; // Heat intensity (0-1)
+ float size; // Heat source size
+ int type; // 0=aircraft, 1=ship, 2=vehicle, 3=building
+ float lastUpdate; // Last update time
+} HeatSource;
+
+static HeatSource gHeatSources[50];
+static int gHeatSourceCount = 0;
+
+ // Additional datarefs for enhanced detection
+ static XPLMDataRef gEngineRunning = NULL;
+ static XPLMDataRef gEngineN1 = NULL;
+static XPLMDataRef gEngineEGT = NULL;
+static XPLMDataRef gGroundTemperature = NULL;
+static XPLMDataRef gWeatherVisibility = NULL;
+static XPLMDataRef gCloudCoverage = NULL;
+static XPLMDataRef gAmbientTemperature = NULL;
+static XPLMDataRef gWindSpeed = NULL;
+static XPLMDataRef gTimeOfDay = NULL;
+static XPLMDataRef gAIAircraftX = NULL;
+static XPLMDataRef gAIAircraftY = NULL;
+static XPLMDataRef gAIAircraftZ = NULL;
+static XPLMDataRef gAIAircraftCount = NULL;
+static XPLMDataRef gLocalDate = NULL;
+static XPLMDataRef gZuluTime = NULL;
+static XPLMDataRef gLatitude = NULL;
+static XPLMDataRef gLongitude = NULL;
+static XPLMDataRef gAltitude = NULL;
+
+ // Function declarations
+ static void ActivateFLIRCallback(void* inRefcon);
+ static void ZoomInCallback(void* inRefcon);
+ static void ZoomOutCallback(void* inRefcon);
+ static void PanLeftCallback(void* inRefcon);
+ static void PanRightCallback(void* inRefcon);
+ static void TiltUpCallback(void* inRefcon);
+ static void TiltDownCallback(void* inRefcon);
+static void ThermalToggleCallback(void* inRefcon);
+static void FocusLockCallback(void* inRefcon);
+
+ static int FLIRCameraFunc(XPLMCameraPosition_t* outCameraPosition,
+ int inIsLosingControl,
+ void* inRefcon);
+
+ static int DrawThermalOverlay(XPLMDrawingPhase inPhase,
+ int inIsBefore,
+ void* inRefcon);
+
+static void UpdateEnvironmentalFactors(void);
+static void UpdateHeatSources(void);
+static void DetectAircraft(void);
+static float CalculateHeatIntensity(float engineTemp, float distance, int engineRunning);
+static void DrawRealisticThermalOverlay(void);
+static float GetDistanceToCamera(float x, float y, float z);
+
+ // Plugin lifecycle functions
+ PLUGIN_API int XPluginStart(char* outName, char* outSig, char* outDesc)
+ {
+ strcpy(outName, "FLIR Camera System");
+ strcpy(outSig, "flir.camera.system");
+ strcpy(outDesc, "Realistic FLIR camera with zoom and thermal overlay");
+
+ // Get aircraft position datarefs
+ gPlaneX = XPLMFindDataRef("sim/flightmodel/position/local_x");
+ gPlaneY = XPLMFindDataRef("sim/flightmodel/position/local_y");
+ gPlaneZ = XPLMFindDataRef("sim/flightmodel/position/local_z");
+ gPlaneHeading = XPLMFindDataRef("sim/flightmodel/position/psi");
+ gPlanePitch = XPLMFindDataRef("sim/flightmodel/position/theta");
+ gPlaneRoll = XPLMFindDataRef("sim/flightmodel/position/phi");
+
+ // Additional datarefs for thermal detection
+ gEngineRunning = XPLMFindDataRef("sim/flightmodel/engine/ENGN_running");
+ gEngineN1 = XPLMFindDataRef("sim/flightmodel/engine/ENGN_N1_");
+ gEngineEGT = XPLMFindDataRef("sim/flightmodel/engine/ENGN_EGT_c");
+ gGroundTemperature = XPLMFindDataRef("sim/weather/temperature_sealevel_c");
+ gWeatherVisibility = XPLMFindDataRef("sim/weather/visibility_reported_m");
+ gCloudCoverage = XPLMFindDataRef("sim/weather/cloud_coverage[0]");
+ gAmbientTemperature = XPLMFindDataRef("sim/weather/temperature_ambient_c");
+ gWindSpeed = XPLMFindDataRef("sim/weather/wind_speed_kt[0]");
+ gTimeOfDay = XPLMFindDataRef("sim/time/local_time_sec");
+ gAIAircraftX = XPLMFindDataRef("sim/multiplayer/position/plane1_x");
+ gAIAircraftY = XPLMFindDataRef("sim/multiplayer/position/plane1_y");
+ gAIAircraftZ = XPLMFindDataRef("sim/multiplayer/position/plane1_z");
+ gAIAircraftCount = XPLMFindDataRef("sim/operation/prefs/mult_max");
+
+ // Additional datarefs for HUD display
+ gLocalDate = XPLMFindDataRef("sim/time/local_date_days");
+ gZuluTime = XPLMFindDataRef("sim/time/zulu_time_sec");
+ gLatitude = XPLMFindDataRef("sim/flightmodel/position/latitude");
+ gLongitude = XPLMFindDataRef("sim/flightmodel/position/longitude");
+ gAltitude = XPLMFindDataRef("sim/flightmodel/position/elevation");
+
+ // Register hotkeys
+ gActivateKey = XPLMRegisterHotKey(XPLM_VK_F9, xplm_DownFlag,
+ "Activate FLIR Camera",
+ ActivateFLIRCallback, NULL);
+
+ gZoomInKey = XPLMRegisterHotKey(XPLM_VK_EQUAL, xplm_DownFlag,
+ "FLIR Zoom In",
+ ZoomInCallback, NULL);
+
+ gZoomOutKey = XPLMRegisterHotKey(XPLM_VK_MINUS, xplm_DownFlag,
+ "FLIR Zoom Out",
+ ZoomOutCallback, NULL);
+
+ gPanLeftKey = XPLMRegisterHotKey(XPLM_VK_LEFT, xplm_DownFlag,
+ "FLIR Pan Left",
+ PanLeftCallback, NULL);
+
+ gPanRightKey = XPLMRegisterHotKey(XPLM_VK_RIGHT, xplm_DownFlag,
+ "FLIR Pan Right",
+ PanRightCallback, NULL);
+
+ gTiltUpKey = XPLMRegisterHotKey(XPLM_VK_UP, xplm_DownFlag,
+ "FLIR Tilt Up",
+ TiltUpCallback, NULL);
+
+ gTiltDownKey = XPLMRegisterHotKey(XPLM_VK_DOWN, xplm_DownFlag,
+ "FLIR Tilt Down",
+ TiltDownCallback, NULL);
+
+ // Register thermal overlay drawing callback - use objects phase for better integration
+ // Register 2D drawing callback for overlay - per SDK guidance for overlays/annotations
+ // Only register when camera is active to minimize performance overhead
+ // Drawing callback will be registered dynamically when camera is activated
+
+ XPLMDebugString("FLIR Camera System: Plugin loaded successfully\n");
+ XPLMDebugString("FLIR Camera System: Press F9 to activate camera\n");
+ XPLMDebugString("FLIR Camera System: MOUSE for smooth pan/tilt, +/- for zoom, arrows for fine adjust, T for thermal, SPACE for target lock\n");
+
+ gThermalToggleKey = XPLMRegisterHotKey(XPLM_VK_T, xplm_DownFlag,
+ "FLIR Thermal Toggle",
+ ThermalToggleCallback, NULL);
+
+ gFocusLockKey = XPLMRegisterHotKey(XPLM_VK_SPACE, xplm_DownFlag,
+ "FLIR Focus/Lock Target",
+ FocusLockCallback, NULL);
+
+ // Note: For text display, use FlyWithLua to read standard X-Plane datarefs
+
+ return 1;
+ }
+
+ PLUGIN_API void XPluginStop(void)
+ {
+ // Unregister hotkeys
+ if (gActivateKey) XPLMUnregisterHotKey(gActivateKey);
+ if (gZoomInKey) XPLMUnregisterHotKey(gZoomInKey);
+ if (gZoomOutKey) XPLMUnregisterHotKey(gZoomOutKey);
+ if (gPanLeftKey) XPLMUnregisterHotKey(gPanLeftKey);
+ if (gPanRightKey) XPLMUnregisterHotKey(gPanRightKey);
+ if (gTiltUpKey) XPLMUnregisterHotKey(gTiltUpKey);
+ if (gTiltDownKey) XPLMUnregisterHotKey(gTiltDownKey);
+ if (gThermalToggleKey) XPLMUnregisterHotKey(gThermalToggleKey);
+ if (gFocusLockKey) XPLMUnregisterHotKey(gFocusLockKey);
+
+ // Stop camera control if active
+ if (gCameraActive) {
+ XPLMDontControlCamera();
+ gCameraActive = 0;
+ }
+
+ XPLMDebugString("FLIR Camera System: Plugin stopped\n");
+ }
+
+ PLUGIN_API void XPluginDisable(void) { }
+ PLUGIN_API int XPluginEnable(void) { return 1; }
+ PLUGIN_API void XPluginReceiveMessage(XPLMPluginID inFromWho, int inMessage, void* inParam) { }
+
+ // Hotkey callbacks
+ static void ActivateFLIRCallback(void* inRefcon)
+ {
+ if (!gCameraActive) {
+ // Activate FLIR camera
+ XPLMDebugString("FLIR Camera System: Activating camera\n");
+
+ // Switch to external view first
+ // XPLMCommandButtonPress(xplm_joy_v_fr1);
+ // XPLMCommandButtonRelease(xplm_joy_v_fr1);
+
+ // Take camera control
+ XPLMControlCamera(xplm_ControlCameraUntilViewChanges, FLIRCameraFunc, NULL);
+ gCameraActive = 1;
+
+ // Register 2D drawing callback for overlays (per SDK guidance)
+ if (!gDrawCallbackRegistered) {
+ XPLMRegisterDrawCallback(DrawThermalOverlay, xplm_Phase_Window, 0, NULL);
+ gDrawCallbackRegistered = 1;
+ XPLMDebugString("FLIR Camera System: 2D overlay callback registered\n");
+ }
+
+ XPLMDebugString("FLIR Camera System: Camera active - mounted under aircraft\n");
+ } else {
+ // Deactivate camera
+ XPLMDontControlCamera();
+ gCameraActive = 0;
+ // Unregister drawing callback to save performance
+ if (gDrawCallbackRegistered) {
+ XPLMUnregisterDrawCallback(DrawThermalOverlay, xplm_Phase_Window, 0, NULL);
+ gDrawCallbackRegistered = 0;
+ XPLMDebugString("FLIR Camera System: 2D overlay callback unregistered\n");
+ }
+
+ XPLMDebugString("FLIR Camera System: Camera deactivated\n");
+ }
+ }
+
+ static void ZoomInCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ // More precise zoom steps: 1.0, 1.5, 2.0, 3.0, 4.0, 6.0, 8.0, 12.0, 16.0, 24.0, 32.0, 48.0, 64.0
+ if (gZoomLevel < 1.5f) gZoomLevel = 1.5f;
+ else if (gZoomLevel < 2.0f) gZoomLevel = 2.0f;
+ else if (gZoomLevel < 3.0f) gZoomLevel = 3.0f;
+ else if (gZoomLevel < 4.0f) gZoomLevel = 4.0f;
+ else if (gZoomLevel < 6.0f) gZoomLevel = 6.0f;
+ else if (gZoomLevel < 8.0f) gZoomLevel = 8.0f;
+ else if (gZoomLevel < 12.0f) gZoomLevel = 12.0f;
+ else if (gZoomLevel < 16.0f) gZoomLevel = 16.0f;
+ else if (gZoomLevel < 24.0f) gZoomLevel = 24.0f;
+ else if (gZoomLevel < 32.0f) gZoomLevel = 32.0f;
+ else if (gZoomLevel < 48.0f) gZoomLevel = 48.0f;
+ else if (gZoomLevel < 64.0f) gZoomLevel = 64.0f;
+ else gZoomLevel = 64.0f; // Maximum zoom
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Zoom %.1fx\n", gZoomLevel);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void ZoomOutCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ // Reverse zoom steps
+ if (gZoomLevel > 48.0f) gZoomLevel = 48.0f;
+ else if (gZoomLevel > 32.0f) gZoomLevel = 32.0f;
+ else if (gZoomLevel > 24.0f) gZoomLevel = 24.0f;
+ else if (gZoomLevel > 16.0f) gZoomLevel = 16.0f;
+ else if (gZoomLevel > 12.0f) gZoomLevel = 12.0f;
+ else if (gZoomLevel > 8.0f) gZoomLevel = 8.0f;
+ else if (gZoomLevel > 6.0f) gZoomLevel = 6.0f;
+ else if (gZoomLevel > 4.0f) gZoomLevel = 4.0f;
+ else if (gZoomLevel > 3.0f) gZoomLevel = 3.0f;
+ else if (gZoomLevel > 2.0f) gZoomLevel = 2.0f;
+ else if (gZoomLevel > 1.5f) gZoomLevel = 1.5f;
+ else gZoomLevel = 1.0f; // Minimum zoom
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Zoom %.1fx\n", gZoomLevel);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void PanLeftCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ gCameraPan -= 0.5f; // Very sensitive control
+ if (gCameraPan < -180.0f) gCameraPan += 360.0f;
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Pan %.1f degrees\n", gCameraPan);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void PanRightCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ gCameraPan += 0.5f; // Very sensitive control
+ if (gCameraPan > 180.0f) gCameraPan -= 360.0f;
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Pan %.1f degrees\n", gCameraPan);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void TiltUpCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ gCameraTilt = fminf(gCameraTilt + 0.5f, 45.0f); // Very sensitive control
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Tilt %.1f degrees\n", gCameraTilt);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void TiltDownCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ gCameraTilt = fmaxf(gCameraTilt - 0.5f, -90.0f); // Very sensitive control
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Tilt %.1f degrees\n", gCameraTilt);
+ XPLMDebugString(msg);
+ }
+ }
+
+ static void ThermalToggleCallback(void* inRefcon)
+ {
+ if (gCameraActive) {
+ gThermalMode = (gThermalMode + 1) % 3;
+ char msg[256];
+ const char* modeNames[] = {"Off", "White Hot", "Enhanced"};
+ sprintf(msg, "FLIR Camera System: Thermal mode %s\n", modeNames[gThermalMode]);
+ XPLMDebugString(msg);
+ }
+ }
+
+ /*
+ * FLIRCameraFunc
+ *
+ * The main camera control function - positions camera under aircraft
+ * like a real belly-mounted FLIR system
+ */
+ static int FLIRCameraFunc(XPLMCameraPosition_t* outCameraPosition,
+ int inIsLosingControl,
+ void* inRefcon)
+ {
+ if (outCameraPosition && !inIsLosingControl) {
+ // Get current aircraft position and orientation
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeY = XPLMGetDataf(gPlaneY);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+ float planeHeading = XPLMGetDataf(gPlaneHeading);
+ // float planePitch = XPLMGetDataf(gPlanePitch); // unused for now
+ float planeRoll = XPLMGetDataf(gPlaneRoll);
+
+ // Convert to radians
+ float headingRad = planeHeading * M_PI / 180.0f;
+ // float pitchRad = planePitch * M_PI / 180.0f; // unused
+ float rollRad = planeRoll * M_PI / 180.0f;
+ // float panRad = gCameraPan * M_PI / 180.0f; // unused
+ // float tiltRad = gCameraTilt * M_PI / 180.0f; // unused
+
+ // Calculate camera position relative to aircraft
+ // Position camera under the aircraft (belly-mounted)
+ float localX = gCameraDistance * cosf(headingRad);
+ float localZ = gCameraDistance * sinf(headingRad);
+ float localY = gCameraHeight; // Below aircraft
+
+ // Apply aircraft roll and pitch to camera position
+ float rotatedX = localX * cosf(rollRad) - localY * sinf(rollRad);
+ float rotatedY = localX * sinf(rollRad) + localY * cosf(rollRad);
+ float rotatedZ = localZ;
+
+ // Final camera position in world coordinates
+ outCameraPosition->x = planeX + rotatedX;
+ outCameraPosition->y = planeY + rotatedY;
+ outCameraPosition->z = planeZ + rotatedZ;
+
+ // Camera orientation - if locked, track target; otherwise manual control
+ if (gTargetLocked && gLockedTargetIndex >= 0 && gLockedTargetIndex < gHeatSourceCount) {
+ // Update target position from heat sources
+ gTargetX = gHeatSources[gLockedTargetIndex].x;
+ gTargetY = gHeatSources[gLockedTargetIndex].y;
+ gTargetZ = gHeatSources[gLockedTargetIndex].z;
+
+ // Calculate angle to target
+ float dx = gTargetX - outCameraPosition->x;
+ float dy = gTargetY - outCameraPosition->y;
+ float dz = gTargetZ - outCameraPosition->z;
+
+ // Calculate heading and pitch to target
+ float targetHeading = atan2f(dz, dx) * 180.0f / M_PI;
+ float groundDistance = sqrtf(dx*dx + dz*dz);
+ float targetPitch = -atan2f(dy, groundDistance) * 180.0f / M_PI;
+
+ // Track the target automatically
+ outCameraPosition->heading = targetHeading;
+ outCameraPosition->pitch = targetPitch;
+ } else {
+ // Manual camera control
+ outCameraPosition->heading = planeHeading + gCameraPan;
+ outCameraPosition->pitch = gCameraTilt;
+ }
+ outCameraPosition->roll = 0.0f; // Keep camera level
+
+ // Apply zoom - this is the key for real magnification!
+ outCameraPosition->zoom = gZoomLevel;
+
+ // Normalize heading
+ if (outCameraPosition->heading > 360.0f) outCameraPosition->heading -= 360.0f;
+ if (outCameraPosition->heading < 0.0f) outCameraPosition->heading += 360.0f;
+ }
+
+ if (inIsLosingControl) {
+ gCameraActive = 0;
+ XPLMDebugString("FLIR Camera System: Lost camera control\n");
+ }
+
+ return gCameraActive;
+ }
+
+ /*
+ * DrawThermalOverlay
+ *
+ * Draws thermal/IR overlay effects on the camera view
+ */
+ static int DrawThermalOverlay(XPLMDrawingPhase inPhase, int inIsBefore, void* inRefcon)
+ {
+ if (!gCameraActive) return 1;
+
+ // Debug output to verify callback is being called
+ static int callCount = 0;
+ if (callCount < 5) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR Camera System: DrawThermalOverlay called, thermal mode: %d\n", gThermalMode);
+ XPLMDebugString(debugMsg);
+ callCount++;
+ }
+
+ // Update environmental factors and heat sources
+ UpdateEnvironmentalFactors();
+ UpdateHeatSources();
+ DetectAircraft();
+
+ // Set up OpenGL for 2D drawing with proper error checking
+ // Get screen dimensions
+ int screenWidth, screenHeight;
+ XPLMGetScreenSize(&screenWidth, &screenHeight);
+
+ if (screenWidth <= 0 || screenHeight <= 0) {
+ return 1; // Avoid drawing with invalid screen dimensions
+ }
+
+ // Save OpenGL state to prevent crashes
+ glPushAttrib(GL_ALL_ATTRIB_BITS);
+
+ // Set up 2D rendering state
+ XPLMSetGraphicsState(0, 0, 0, 1, 1, 0, 0);
+
+ // Window phase provides proper 2D setup - no manual matrix setup needed
+
+ // For Window phase, coordinate system is already set up by X-Plane
+
+ float centerX = screenWidth / 2.0f;
+ float centerY = screenHeight / 2.0f;
+
+ // Mouse control for camera movement
+ if (gCameraActive && !gTargetLocked) { // Only when not locked
+ int mouseX, mouseY;
+ XPLMGetMouseLocation(&mouseX, &mouseY);
+
+ static int mouseInitialized = 0;
+ if (!mouseInitialized) {
+ gLastMouseX = mouseX;
+ gLastMouseY = mouseY;
+ mouseInitialized = 1;
+ }
+
+ // Calculate mouse delta
+ int deltaX = mouseX - gLastMouseX;
+ int deltaY = mouseY - gLastMouseY;
+
+ // Apply mouse movement to camera (much more sensitive)
+ if (abs(deltaX) > 1 || abs(deltaY) > 1) { // Only if significant movement
+ gCameraPan += deltaX * gMouseSensitivity;
+ gCameraTilt += deltaY * gMouseSensitivity;
+
+ // Clamp values
+ if (gCameraPan > 180.0f) gCameraPan -= 360.0f;
+ if (gCameraPan < -180.0f) gCameraPan += 360.0f;
+ gCameraTilt = fmaxf(-90.0f, fminf(45.0f, gCameraTilt));
+
+ gLastMouseX = mouseX;
+ gLastMouseY = mouseY;
+ }
+ }
+
+ // Draw targeting reticle
+ if (callCount < 3) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Drawing crosshair at %.0f,%.0f (screen %dx%d)\n", centerX, centerY, screenWidth, screenHeight);
+ XPLMDebugString(debugMsg);
+ }
+
+ glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
+ glLineWidth(3.0f);
+
+ glBegin(GL_LINES);
+ // No crosshair - only brackets
+
+ // Military-style targeting brackets [ ] - FIXED SIZE
+ float bracketSize = 50.0f; // Fixed size - doesn't change with zoom
+ float bracketLength = 20.0f;
+
+ // Top-left bracket [
+ glVertex2f(centerX - bracketSize, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize + bracketLength, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize, centerY + bracketSize);
+ glVertex2f(centerX - bracketSize, centerY + bracketSize - bracketLength);
+
+ // Top-right bracket ]
+ glVertex2f(centerX + bracketSize, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize - bracketLength, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize, centerY + bracketSize);
+ glVertex2f(centerX + bracketSize, centerY + bracketSize - bracketLength);
+
+ // Bottom-left bracket [
+ glVertex2f(centerX - bracketSize, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize + bracketLength, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize, centerY - bracketSize);
+ glVertex2f(centerX - bracketSize, centerY - bracketSize + bracketLength);
+
+ // Bottom-right bracket ]
+ glVertex2f(centerX + bracketSize, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize - bracketLength, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize, centerY - bracketSize);
+ glVertex2f(centerX + bracketSize, centerY - bracketSize + bracketLength);
+
+ // Small center dot for precise aiming
+ glVertex2f(centerX - 2, centerY);
+ glVertex2f(centerX + 2, centerY);
+ glVertex2f(centerX, centerY - 2);
+ glVertex2f(centerX, centerY + 2);
+ glEnd();
+
+ // Draw thermal effects based on thermal mode - using same reliable approach as crosshair
+ if (gThermalMode > 0) {
+ if (callCount < 3) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Drawing thermal overlay mode %d\n", gThermalMode);
+ XPLMDebugString(debugMsg);
+ }
+
+ // Full-screen thermal overlay using same drawing method as crosshair
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Draw thermal background overlay
+ if (gThermalMode == 1) {
+ // White Hot mode - dark blue/black background
+ glColor4f(0.05f, 0.1f, 0.2f, 0.7f); // More opaque for realistic IR view
+ } else {
+ // Enhanced mode - greenish IR background
+ glColor4f(0.1f, 0.3f, 0.1f, 0.6f);
+ }
+
+ glBegin(GL_QUADS);
+ glVertex2f(0, 0);
+ glVertex2f(screenWidth, 0);
+ glVertex2f(screenWidth, screenHeight);
+ glVertex2f(0, screenHeight);
+ glEnd();
+
+ // Realistic IR scan lines only (no random noise)
+ glLineWidth(1.0f);
+ glColor4f(0.0f, 0.0f, 0.0f, 0.15f);
+
+ // Horizontal scan lines like real IR cameras
+ for (int y = 0; y < screenHeight; y += 2) {
+ glBegin(GL_LINES);
+ glVertex2f(0, y);
+ glVertex2f(screenWidth, y);
+ glEnd();
+ }
+
+ // Only draw realistic heat sources from actual detection, no fake test rectangles
+
+ // Draw realistic heat sources if we have any
+ if (gHeatSourceCount > 0) {
+ DrawRealisticThermalOverlay();
+ }
+
+ // No background thermal noise - cleaner IR view
+
+ glDisable(GL_BLEND);
+ }
+
+ // Draw realistic military FLIR HUD overlay
+ glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
+ glLineWidth(1.0f);
+
+ // Minimal corner reticles only (like real FLIR systems)
+ float reticleSize = 15.0f;
+ glBegin(GL_LINES);
+ // Top-left reticle
+ glVertex2f(30, 30); glVertex2f(30 + reticleSize, 30);
+ glVertex2f(30, 30); glVertex2f(30, 30 + reticleSize);
+ // Top-right reticle
+ glVertex2f(screenWidth - 30 - reticleSize, 30); glVertex2f(screenWidth - 30, 30);
+ glVertex2f(screenWidth - 30, 30); glVertex2f(screenWidth - 30, 30 + reticleSize);
+ // Bottom-left reticle
+ glVertex2f(30, screenHeight - 30 - reticleSize); glVertex2f(30, screenHeight - 30);
+ glVertex2f(30, screenHeight - 30); glVertex2f(30 + reticleSize, screenHeight - 30);
+ // Bottom-right reticle
+ glVertex2f(screenWidth - 30 - reticleSize, screenHeight - 30); glVertex2f(screenWidth - 30, screenHeight - 30);
+ glVertex2f(screenWidth - 30, screenHeight - 30); glVertex2f(screenWidth - 30, screenHeight - 30 - reticleSize);
+ glEnd();
+
+ // Data will be exported to shared datarefs for FlyWithLua display
+ // For now, minimal built-in display only
+
+ // Use X-Plane's text rendering for professional look
+ glColor4f(0.0f, 1.0f, 0.0f, 1.0f);
+
+ // Minimal status indicators (no confusing abstract lines)
+ // Only show essential info
+
+ // Zoom level indicator (bottom-left corner only)
+ glLineWidth(2.0f);
+ glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
+ glBegin(GL_LINES);
+ float zoomBar = fminf(gZoomLevel * 5.0f, 100.0f); // Scale zoom to reasonable bar length
+ glVertex2f(30, 40);
+ glVertex2f(30 + zoomBar, 40);
+ glEnd();
+
+ // TARGET LOCK INDICATOR - VERY VISIBLE
+ if (gTargetLocked) {
+ glColor4f(1.0f, 0.0f, 0.0f, 1.0f); // Bright red for locked
+ glLineWidth(4.0f);
+
+ // Large red box around the entire targeting area
+ glBegin(GL_LINE_LOOP);
+ glVertex2f(centerX - 100, centerY - 100);
+ glVertex2f(centerX + 100, centerY - 100);
+ glVertex2f(centerX + 100, centerY + 100);
+ glVertex2f(centerX - 100, centerY + 100);
+ glEnd();
+
+ // Flashing corner indicators
+ float time = XPLMGetElapsedTime();
+ float flash = (sinf(time * 4.0f) + 1.0f) * 0.5f; // 0-1 flashing
+ glColor4f(1.0f, 0.0f, 0.0f, 0.5f + flash * 0.5f);
+
+ glBegin(GL_LINES);
+ float lockSize = 30.0f;
+ // Top-left corner
+ glVertex2f(centerX - 100, centerY + 100 - lockSize); glVertex2f(centerX - 100, centerY + 100);
+ glVertex2f(centerX - 100, centerY + 100); glVertex2f(centerX - 100 + lockSize, centerY + 100);
+ // Top-right corner
+ glVertex2f(centerX + 100 - lockSize, centerY + 100); glVertex2f(centerX + 100, centerY + 100);
+ glVertex2f(centerX + 100, centerY + 100); glVertex2f(centerX + 100, centerY + 100 - lockSize);
+ // Bottom-left corner
+ glVertex2f(centerX - 100, centerY - 100 + lockSize); glVertex2f(centerX - 100, centerY - 100);
+ glVertex2f(centerX - 100, centerY - 100); glVertex2f(centerX - 100 + lockSize, centerY - 100);
+ // Bottom-right corner
+ glVertex2f(centerX + 100 - lockSize, centerY - 100); glVertex2f(centerX + 100, centerY - 100);
+ glVertex2f(centerX + 100, centerY - 100); glVertex2f(centerX + 100, centerY - 100 + lockSize);
+ glEnd();
+
+ // LOCKED text indicator (top of screen)
+ glColor4f(1.0f, 0.0f, 0.0f, 1.0f);
+ glLineWidth(3.0f);
+ glBegin(GL_LINES);
+ // Simple "LOCKED" text using lines at top center
+ float textY = screenHeight - 100;
+ // L
+ glVertex2f(centerX - 80, textY); glVertex2f(centerX - 80, textY - 20);
+ glVertex2f(centerX - 80, textY - 20); glVertex2f(centerX - 65, textY - 20);
+ // O
+ glVertex2f(centerX - 60, textY); glVertex2f(centerX - 45, textY);
+ glVertex2f(centerX - 60, textY - 20); glVertex2f(centerX - 45, textY - 20);
+ glVertex2f(centerX - 60, textY); glVertex2f(centerX - 60, textY - 20);
+ glVertex2f(centerX - 45, textY); glVertex2f(centerX - 45, textY - 20);
+ // C
+ glVertex2f(centerX - 40, textY); glVertex2f(centerX - 25, textY);
+ glVertex2f(centerX - 40, textY - 20); glVertex2f(centerX - 25, textY - 20);
+ glVertex2f(centerX - 40, textY); glVertex2f(centerX - 40, textY - 20);
+ // K
+ glVertex2f(centerX - 20, textY); glVertex2f(centerX - 20, textY - 20);
+ glVertex2f(centerX - 20, textY - 10); glVertex2f(centerX - 5, textY);
+ glVertex2f(centerX - 20, textY - 10); glVertex2f(centerX - 5, textY - 20);
+ // E
+ glVertex2f(centerX, textY); glVertex2f(centerX, textY - 20);
+ glVertex2f(centerX, textY); glVertex2f(centerX + 15, textY);
+ glVertex2f(centerX, textY - 10); glVertex2f(centerX + 10, textY - 10);
+ glVertex2f(centerX, textY - 20); glVertex2f(centerX + 15, textY - 20);
+ // D
+ glVertex2f(centerX + 20, textY); glVertex2f(centerX + 20, textY - 20);
+ glVertex2f(centerX + 20, textY); glVertex2f(centerX + 30, textY - 5);
+ glVertex2f(centerX + 30, textY - 5); glVertex2f(centerX + 30, textY - 15);
+ glVertex2f(centerX + 30, textY - 15); glVertex2f(centerX + 20, textY - 20);
+ glEnd();
+ }
+
+ // No range circles - clean display
+
+ // Heading indicator (top center)
+ glColor4f(0.0f, 1.0f, 0.0f, 0.9f);
+ glLineWidth(2.0f);
+ float headingX = centerX;
+ float headingY = screenHeight - 40;
+ float currentHeading = XPLMGetDataf(gPlaneHeading);
+
+ glBegin(GL_LINES);
+ // Heading scale
+ for (int h = 0; h < 360; h += 30) {
+ float angle = (h - currentHeading - gCameraPan) * M_PI / 180.0f;
+ float x1 = headingX + 100 * sinf(angle);
+ float x2 = headingX + 110 * sinf(angle);
+ if (x1 > 50 && x1 < screenWidth - 50) {
+ glVertex2f(x1, headingY);
+ glVertex2f(x2, headingY);
+ }
+ }
+ glEnd();
+
+ // Center heading marker
+ glBegin(GL_LINES);
+ glVertex2f(headingX, headingY - 10);
+ glVertex2f(headingX, headingY + 10);
+ glEnd();
+
+ // Restore OpenGL state to prevent crashes
+ glPopAttrib();
+
+ return 1;
+ }
+
+ /*
+ * Additional utility functions for future enhancements
+ */
+
+ // Function to detect and track actual X-Plane objects
+ /*
+static void UpdateTargetTracking(void)
+ {
+ // Future enhancement: Use X-Plane's object detection APIs
+ // to find and track actual aircraft, ships, vehicles in the sim
+ }
+
+ // Function to simulate different thermal/IR modes
+ static void SetThermalMode(int mode)
+ {
+ // Future enhancement: Different thermal rendering modes
+ // 0 = White Hot, 1 = Black Hot, 2 = Rainbow, etc.
+ }
+*/
+/*
+ * Enhanced Functions for FLIR Camera Plugin
+ * These functions provide realistic thermal detection and environmental factors
+ */
+
+/*
+ * UpdateEnvironmentalFactors
+ *
+ * Updates environmental conditions that affect thermal visibility
+ */
+static void UpdateEnvironmentalFactors(void)
+{
+ if (gWeatherVisibility != NULL) {
+ gCurrentVisibility = XPLMGetDataf(gWeatherVisibility);
+ }
+
+ if (gAmbientTemperature != NULL) {
+ gCurrentTemp = XPLMGetDataf(gAmbientTemperature);
+ }
+
+ if (gWindSpeed != NULL) {
+ gCurrentWindSpeed = XPLMGetDataf(gWindSpeed);
+ }
+
+ if (gTimeOfDay != NULL) {
+ float timeOfDay = XPLMGetDataf(gTimeOfDay);
+ // Night is roughly 18:00 to 06:00 (18*3600 = 64800, 6*3600 = 21600)
+ gIsNight = (timeOfDay > 64800.0f || timeOfDay < 21600.0f) ? 1 : 0;
+ }
+}
+
+/*
+ * GetDistanceToCamera
+ *
+ * Calculate distance from world position to camera
+ */
+static float GetDistanceToCamera(float x, float y, float z)
+{
+ float camX = XPLMGetDataf(gPlaneX);
+ float camY = XPLMGetDataf(gPlaneY);
+ float camZ = XPLMGetDataf(gPlaneZ);
+
+ float dx = x - camX;
+ float dy = y - camY;
+ float dz = z - camZ;
+
+ return sqrtf(dx*dx + dy*dy + dz*dz);
+}
+
+/*
+ * CalculateHeatIntensity
+ *
+ * Calculate thermal intensity based on engine temperature and distance
+ */
+static float CalculateHeatIntensity(float engineTemp, float distance, int engineRunning)
+{
+ if (!engineRunning) return 0.1f; // Cold engine still slightly visible
+
+ // Base intensity from engine temperature (EGT typically 400-900°C)
+ float baseIntensity = (engineTemp - 200.0f) / 700.0f;
+ baseIntensity = fmaxf(0.0f, fminf(1.0f, baseIntensity));
+
+ // Distance attenuation (max detection ~10km for aircraft)
+ float distanceFactor = 1.0f - (distance / 10000.0f);
+ distanceFactor = fmaxf(0.0f, distanceFactor);
+
+ // Weather effects
+ float weatherFactor = gCurrentVisibility / 10000.0f;
+ weatherFactor = fmaxf(0.2f, fminf(1.0f, weatherFactor));
+
+ // Night enhances thermal contrast
+ float nightBonus = gIsNight ? 1.3f : 1.0f;
+
+ return baseIntensity * distanceFactor * weatherFactor * nightBonus;
+}
+
+/*
+ * DetectAircraft
+ *
+ * Scan for AI aircraft and other heat sources in the simulation
+ */
+static void DetectAircraft(void)
+{
+ gAircraftCount = 0;
+
+ // Debug output for aircraft detection
+ static int detectCallCount = 0;
+ detectCallCount++;
+ if (detectCallCount % 300 == 1) { // Every ~5 seconds
+ XPLMDebugString("FLIR Camera System: Scanning for AI aircraft and ships...\n");
+ }
+
+ // **1. Detect AI Aircraft (much more common than multiplayer)**
+ // X-Plane has up to 63 AI aircraft slots
+ for (int i = 0; i < 63 && gAircraftCount < 20; i++) {
+ char datarefName[256];
+
+ // AI aircraft positions
+ sprintf(datarefName, "sim/multiplayer/position/plane%d_x", i + 1);
+ XPLMDataRef aircraftX = XPLMFindDataRef(datarefName);
+ sprintf(datarefName, "sim/multiplayer/position/plane%d_y", i + 1);
+ XPLMDataRef aircraftY = XPLMFindDataRef(datarefName);
+ sprintf(datarefName, "sim/multiplayer/position/plane%d_z", i + 1);
+ XPLMDataRef aircraftZ = XPLMFindDataRef(datarefName);
+
+ if (aircraftX && aircraftY && aircraftZ) {
+ float x = XPLMGetDataf(aircraftX);
+ float y = XPLMGetDataf(aircraftY);
+ float z = XPLMGetDataf(aircraftZ);
+
+ // Check if aircraft exists and is not our own plane
+ if ((x != 0.0f || y != 0.0f || z != 0.0f)) {
+ float distance = GetDistanceToCamera(x, y, z);
+ if (distance > 50.0f) { // Minimum distance to avoid locking on self
+ gAircraftPositions[gAircraftCount][0] = x;
+ gAircraftPositions[gAircraftCount][1] = y;
+ gAircraftPositions[gAircraftCount][2] = z;
+ gAircraftDistance[gAircraftCount] = distance;
+
+ // Simulate engine temperature (realistic EGT values)
+ gAircraftEngineTemp[gAircraftCount] = 450.0f + (rand() % 300);
+
+ // Determine visibility based on distance and conditions
+ gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility * 0.8f) ? 1 : 0;
+
+ if (detectCallCount % 300 == 1) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Found AI aircraft %d at %.0fm\n", i+1, distance);
+ XPLMDebugString(debugMsg);
+ }
+
+ gAircraftCount++;
+ }
+ }
+ }
+ }
+
+ // **2. Detect Ships - try multiple ship/boat datarefs**
+
+ // Method 1: Try ground vehicle datarefs that might be ships
+ XPLMDataRef shipX = XPLMFindDataRef("sim/flightmodel2/position/local_x");
+ XPLMDataRef shipY = XPLMFindDataRef("sim/flightmodel2/position/local_y");
+ XPLMDataRef shipZ = XPLMFindDataRef("sim/flightmodel2/position/local_z");
+
+ if (shipX && shipY && shipZ && gAircraftCount < 20) {
+ float x = XPLMGetDataf(shipX);
+ float y = XPLMGetDataf(shipY);
+ float z = XPLMGetDataf(shipZ);
+
+ if (x != 0.0f || y != 0.0f || z != 0.0f) {
+ float distance = GetDistanceToCamera(x, y, z);
+ if (distance > 50.0f) {
+ gAircraftPositions[gAircraftCount][0] = x;
+ gAircraftPositions[gAircraftCount][1] = y;
+ gAircraftPositions[gAircraftCount][2] = z;
+ gAircraftDistance[gAircraftCount] = distance;
+ gAircraftEngineTemp[gAircraftCount] = 250.0f + (rand() % 150); // Ships have lower heat
+ gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility) ? 1 : 0;
+
+ if (detectCallCount % 300 == 1) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Found ship/vehicle at %.0fm\n", distance);
+ XPLMDebugString(debugMsg);
+ }
+ gAircraftCount++;
+ }
+ }
+ }
+
+ // Method 2: AI boat datarefs
+ for (int i = 0; i < 10 && gAircraftCount < 20; i++) {
+ char datarefName[256];
+ sprintf(datarefName, "sim/multiplayer/position/boat%d_x", i + 1);
+ XPLMDataRef boatX = XPLMFindDataRef(datarefName);
+ sprintf(datarefName, "sim/multiplayer/position/boat%d_y", i + 1);
+ XPLMDataRef boatY = XPLMFindDataRef(datarefName);
+ sprintf(datarefName, "sim/multiplayer/position/boat%d_z", i + 1);
+ XPLMDataRef boatZ = XPLMFindDataRef(datarefName);
+
+ if (boatX && boatY && boatZ) {
+ float x = XPLMGetDataf(boatX);
+ float y = XPLMGetDataf(boatY);
+ float z = XPLMGetDataf(boatZ);
+
+ if (x != 0.0f || y != 0.0f || z != 0.0f) {
+ float distance = GetDistanceToCamera(x, y, z);
+ if (distance > 50.0f) {
+ gAircraftPositions[gAircraftCount][0] = x;
+ gAircraftPositions[gAircraftCount][1] = y;
+ gAircraftPositions[gAircraftCount][2] = z;
+ gAircraftDistance[gAircraftCount] = distance;
+
+ // Ships have engine heat too
+ gAircraftEngineTemp[gAircraftCount] = 300.0f + (rand() % 200);
+ gAircraftVisible[gAircraftCount] = (distance < gCurrentVisibility) ? 1 : 0;
+
+ if (detectCallCount % 300 == 1) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Found ship %d at %.0fm\n", i+1, distance);
+ XPLMDebugString(debugMsg);
+ }
+
+ gAircraftCount++;
+ }
+ }
+ }
+ }
+
+ if (detectCallCount % 300 == 1) {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR: Total targets found: %d\n", gAircraftCount);
+ XPLMDebugString(debugMsg);
+ }
+}
+
+/*
+ * UpdateHeatSources
+ *
+ * Update dynamic heat sources in the simulation
+ */
+static void UpdateHeatSources(void)
+{
+ float currentTime = XPLMGetElapsedTime();
+ gHeatSourceCount = 0;
+
+ // Add detected aircraft as heat sources
+ for (int i = 0; i < gAircraftCount && gHeatSourceCount < 50; i++) {
+ if (gAircraftVisible[i]) {
+ gHeatSources[gHeatSourceCount].x = gAircraftPositions[i][0];
+ gHeatSources[gHeatSourceCount].y = gAircraftPositions[i][1];
+ gHeatSources[gHeatSourceCount].z = gAircraftPositions[i][2];
+ gHeatSources[gHeatSourceCount].intensity = CalculateHeatIntensity(
+ gAircraftEngineTemp[i], gAircraftDistance[i], 1);
+ gHeatSources[gHeatSourceCount].size = 15.0f + (gHeatSources[gHeatSourceCount].intensity * 25.0f);
+ gHeatSources[gHeatSourceCount].type = 0; // Aircraft
+ gHeatSources[gHeatSourceCount].lastUpdate = currentTime;
+ gHeatSourceCount++;
+ }
+ }
+
+ // Add simulated ground heat sources (buildings, vehicles, ships)
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+
+ // Simulate some heat sources around the area
+ for (int i = 0; i < 8 && gHeatSourceCount < 50; i++) {
+ float angle = (i * 45.0f) * M_PI / 180.0f;
+ float distance = 2000.0f + (i * 500.0f);
+
+ float x = planeX + distance * cosf(angle);
+ float z = planeZ + distance * sinf(angle);
+ float y = XPLMGetDataf(gPlaneY) - 100.0f; // Ground level
+
+ gHeatSources[gHeatSourceCount].x = x;
+ gHeatSources[gHeatSourceCount].y = y;
+ gHeatSources[gHeatSourceCount].z = z;
+
+ // Vary intensity based on type and time
+ float baseIntensity = 0.3f;
+ if (gIsNight) baseIntensity += 0.2f; // More visible at night
+
+ gHeatSources[gHeatSourceCount].intensity = baseIntensity +
+ 0.2f * sinf(currentTime * 0.1f + i);
+ gHeatSources[gHeatSourceCount].size = 8.0f + (gHeatSources[gHeatSourceCount].intensity * 12.0f);
+ gHeatSources[gHeatSourceCount].type = (i % 3) + 1; // Ships, vehicles, buildings
+ gHeatSources[gHeatSourceCount].lastUpdate = currentTime;
+ gHeatSourceCount++;
+ }
+}
+
+/*
+ * DrawRealisticThermalOverlay
+ *
+ * Render realistic thermal signatures for detected heat sources
+ */
+static void DrawRealisticThermalOverlay(void)
+{
+ if (gHeatSourceCount == 0) return;
+
+ // Get screen dimensions and camera info
+ int screenWidth, screenHeight;
+ XPLMGetScreenSize(&screenWidth, &screenHeight);
+
+ float camX = XPLMGetDataf(gPlaneX);
+ float camY = XPLMGetDataf(gPlaneY);
+ float camZ = XPLMGetDataf(gPlaneZ);
+ float camHeading = XPLMGetDataf(gPlaneHeading) + gCameraPan;
+ float camPitch = gCameraTilt;
+
+ // Convert camera angles to radians
+ float headingRad = camHeading * M_PI / 180.0f;
+ float pitchRad = camPitch * M_PI / 180.0f;
+
+ // Camera forward vector
+ float forwardX = cosf(headingRad) * cosf(pitchRad);
+ float forwardY = sinf(pitchRad);
+ float forwardZ = sinf(headingRad) * cosf(pitchRad);
+
+ // Camera right vector (for screen mapping)
+ float rightX = -sinf(headingRad);
+ float rightZ = cosf(headingRad);
+
+ // Camera up vector
+ float upX = -cosf(headingRad) * sinf(pitchRad);
+ float upY = cosf(pitchRad);
+ float upZ = -sinf(headingRad) * sinf(pitchRad);
+
+ // Field of view factor (approximate)
+ float fovFactor = 60.0f / gZoomLevel; // Base FOV 60 degrees
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ // Draw each heat source
+ for (int i = 0; i < gHeatSourceCount; i++) {
+ HeatSource* heat = &gHeatSources[i];
+
+ // Vector from camera to heat source
+ float dx = heat->x - camX;
+ float dy = heat->y - camY;
+ float dz = heat->z - camZ;
+ float distance = sqrtf(dx*dx + dy*dy + dz*dz);
+
+ if (distance < 50.0f) continue; // Too close, skip
+
+ // Normalize direction vector
+ dx /= distance;
+ dy /= distance;
+ dz /= distance;
+
+ // Check if heat source is in front of camera
+ float dotForward = dx * forwardX + dy * forwardY + dz * forwardZ;
+ if (dotForward <= 0.1f) continue; // Behind camera or too far to side
+
+ // Project to screen coordinates
+ float rightDot = dx * rightX + dz * rightZ;
+ float upDot = dx * upX + dy * upY + dz * upZ;
+
+ // Convert to screen space
+ float screenX = screenWidth * 0.5f + (rightDot * screenWidth * 0.5f / fovFactor);
+ float screenY = screenHeight * 0.5f + (upDot * screenHeight * 0.5f / fovFactor);
+
+ // Check if on screen
+ if (screenX < 0 || screenX >= screenWidth || screenY < 0 || screenY >= screenHeight) {
+ continue;
+ }
+
+ // Calculate apparent size based on distance and zoom
+ float apparentSize = heat->size * gZoomLevel / (distance * 0.01f);
+ apparentSize = fmaxf(3.0f, fminf(apparentSize, 50.0f));
+
+ // Color based on heat source type and intensity
+ float r, g, b, a;
+ if (gThermalMode == 1) {
+ // White hot mode
+ r = g = b = heat->intensity;
+ a = heat->intensity * 0.8f;
+
+ // Aircraft show as very bright white
+ if (heat->type == 0) {
+ r = g = b = 1.0f;
+ a = 0.9f;
+ }
+ } else {
+ // Enhanced mode - use color coding
+ switch (heat->type) {
+ case 0: // Aircraft - bright yellow/orange
+ r = 1.0f;
+ g = 0.8f;
+ b = 0.2f;
+ a = heat->intensity * 0.9f;
+ break;
+ case 1: // Ships - cyan
+ r = 0.2f;
+ g = 0.8f;
+ b = 1.0f;
+ a = heat->intensity * 0.7f;
+ break;
+ case 2: // Vehicles - red
+ r = 1.0f;
+ g = 0.3f;
+ b = 0.1f;
+ a = heat->intensity * 0.6f;
+ break;
+ case 3: // Buildings - orange
+ r = 1.0f;
+ g = 0.5f;
+ b = 0.0f;
+ a = heat->intensity * 0.5f;
+ break;
+ default:
+ r = g = b = heat->intensity;
+ a = heat->intensity * 0.6f;
+ break;
+ }
+ }
+
+ glColor4f(r, g, b, a);
+
+ // Draw heat signature with gradient effect
+ glBegin(GL_QUADS);
+ glVertex2f(screenX - apparentSize, screenY - apparentSize);
+ glVertex2f(screenX + apparentSize, screenY - apparentSize);
+ glVertex2f(screenX + apparentSize, screenY + apparentSize);
+ glVertex2f(screenX - apparentSize, screenY + apparentSize);
+ glEnd();
+
+ // Add heat bloom effect for intense sources
+ if (heat->intensity > 0.7f) {
+ float bloomSize = apparentSize * 1.5f;
+ glColor4f(r, g, b, a * 0.3f);
+ glBegin(GL_QUADS);
+ glVertex2f(screenX - bloomSize, screenY - bloomSize);
+ glVertex2f(screenX + bloomSize, screenY - bloomSize);
+ glVertex2f(screenX + bloomSize, screenY + bloomSize);
+ glVertex2f(screenX - bloomSize, screenY + bloomSize);
+ glEnd();
+ }
+
+ // Add targeting indicator for aircraft
+ if (heat->type == 0 && distance < 5000.0f) {
+ glColor4f(0.0f, 1.0f, 0.0f, 0.8f);
+ glLineWidth(2.0f);
+
+ float targetSize = apparentSize + 10.0f;
+ glBegin(GL_LINE_LOOP);
+ glVertex2f(screenX - targetSize, screenY - targetSize);
+ glVertex2f(screenX + targetSize, screenY - targetSize);
+ glVertex2f(screenX + targetSize, screenY + targetSize);
+ glVertex2f(screenX - targetSize, screenY + targetSize);
+ glEnd();
+
+ // Add distance indicator
+ char distText[32];
+ sprintf(distText, "%.0fm", distance);
+ // Note: Actual text rendering would need XPLMDrawString
+ }
+ }
+
+ glDisable(GL_BLEND);
+}
+
+static void FocusLockCallback(void* inRefcon)
+{
+ if (gCameraActive) {
+ if (!gTargetLocked) {
+ // Find nearest target to focus on
+ float nearestDistance = 999999.0f;
+ int nearestTarget = -1;
+
+ // Check heat sources for nearest aircraft
+ for (int i = 0; i < gHeatSourceCount; i++) {
+ if (gHeatSources[i].type == 0) { // Aircraft
+ float distance = GetDistanceToCamera(gHeatSources[i].x, gHeatSources[i].y, gHeatSources[i].z);
+ if (distance < nearestDistance && distance > 100.0f) { // Minimum 100m distance
+ nearestDistance = distance;
+ nearestTarget = i;
+ }
+ }
+ }
+
+ if (nearestTarget >= 0) {
+ gTargetLocked = 1;
+ gLockedTargetIndex = nearestTarget;
+ gFocusDistance = nearestDistance;
+ gTargetX = gHeatSources[nearestTarget].x;
+ gTargetY = gHeatSources[nearestTarget].y;
+ gTargetZ = gHeatSources[nearestTarget].z;
+ char msg[256];
+ sprintf(msg, "FLIR Camera System: Target LOCKED at %.0fm - Camera will track automatically\n", gFocusDistance);
+ XPLMDebugString(msg);
+ } else {
+ char debugMsg[256];
+ sprintf(debugMsg, "FLIR Camera System: No targets found - Heat sources: %d, Aircraft: %d\n",
+ gHeatSourceCount, gAircraftCount);
+ XPLMDebugString(debugMsg);
+ }
+ } else {
+ // Unlock target
+ gTargetLocked = 0;
+ gLockedTargetIndex = -1;
+ XPLMDebugString("FLIR Camera System: Target UNLOCKED - Manual control restored\n");
+ }
+ }
+}
\ No newline at end of file
diff --git a/FLIR_HUD.lua b/FLIR_HUD.lua
index 33b6864..21f9351 100644
--- a/FLIR_HUD.lua
+++ b/FLIR_HUD.lua
@@ -1,15 +1,18 @@
-- FLIR Camera HUD Display Script for FlyWithLua
-- Place this file in X-Plane/Resources/plugins/FlyWithLua/Scripts/
--- Simple and safe version
+-- Only shows HUD when FLIR camera is actually active
-- Global variables
flir_start_time = flir_start_time or os.time()
function draw_flir_hud()
- -- Only draw when in external view (likely FLIR active)
+ -- Check if FLIR camera is active via shared dataref
+ local flir_active = dataref_table("sim/operation/prefs/misc/manipulator_disabled")
+
+ -- Only draw HUD when FLIR camera is actually active (manipulator disabled indicates camera mode)
local view_type = dataref_table("sim/graphics/view/view_type")
- if view_type[0] == 1026 then -- External view
+ if view_type[0] == 1026 and flir_active[0] == 1 then -- External view AND camera active
-- Get basic aircraft data safely
local zulu_time = dataref_table("sim/time/zulu_time_sec")[0]
local latitude = dataref_table("sim/flightmodel/position/latitude")[0]
diff --git a/FLIR_LockOn.cpp b/FLIR_LockOn.cpp
new file mode 100644
index 0000000..b8d2c90
--- /dev/null
+++ b/FLIR_LockOn.cpp
@@ -0,0 +1,164 @@
+/*
+ * FLIR_LockOn.cpp
+ *
+ * Arbitrary point focus system for FLIR camera
+ * This module handles camera locking to specific world coordinates
+ * without AI target scanning - focuses on arbitrary points in space
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <math.h>
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+#include "XPLMDataAccess.h"
+#include "XPLMUtilities.h"
+#include "FLIR_LockOn.h"
+
+// Lock-on state
+static int gLockOnActive = 0;
+static float gLockedWorldX = 0.0f;
+static float gLockedWorldY = 0.0f;
+static float gLockedWorldZ = 0.0f;
+static float gLockOnPan = 0.0f; // Camera pan when locked
+static float gLockOnTilt = 0.0f; // Camera tilt when locked
+
+// Aircraft position datarefs
+static XPLMDataRef gPlaneX = NULL;
+static XPLMDataRef gPlaneY = NULL;
+static XPLMDataRef gPlaneZ = NULL;
+static XPLMDataRef gPlaneHeading = NULL;
+static XPLMDataRef gPlanePitch = NULL;
+static XPLMDataRef gPlaneRoll = NULL;
+
+void InitializeLockOnSystem()
+{
+ // Get aircraft position datarefs
+ gPlaneX = XPLMFindDataRef("sim/flightmodel/position/local_x");
+ gPlaneY = XPLMFindDataRef("sim/flightmodel/position/local_y");
+ gPlaneZ = XPLMFindDataRef("sim/flightmodel/position/local_z");
+ gPlaneHeading = XPLMFindDataRef("sim/flightmodel/position/psi");
+ gPlanePitch = XPLMFindDataRef("sim/flightmodel/position/theta");
+ gPlaneRoll = XPLMFindDataRef("sim/flightmodel/position/phi");
+
+ XPLMDebugString("FLIR Lock-On System: Initialized\n");
+}
+
+void SetArbitraryLockPoint(float currentPan, float currentTilt, float distance)
+{
+ if (!gPlaneX || !gPlaneY || !gPlaneZ || !gPlaneHeading) {
+ XPLMDebugString("FLIR Lock-On: Aircraft datarefs not available\n");
+ return;
+ }
+
+ // Get current aircraft position
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeY = XPLMGetDataf(gPlaneY);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+ float planeHeading = XPLMGetDataf(gPlaneHeading);
+
+ // Convert camera angles to radians
+ float panRad = (planeHeading + currentPan) * M_PI / 180.0f;
+ float tiltRad = currentTilt * M_PI / 180.0f;
+
+ // Calculate world coordinates of the lock point
+ // Project forward from camera position based on current pan/tilt
+ gLockedWorldX = planeX + distance * sin(panRad) * cos(tiltRad);
+ gLockedWorldY = planeY + distance * sin(tiltRad);
+ gLockedWorldZ = planeZ + distance * cos(panRad) * cos(tiltRad);
+
+ // Store the camera angles at lock time
+ gLockOnPan = currentPan;
+ gLockOnTilt = currentTilt;
+
+ gLockOnActive = 1;
+
+ char msg[256];
+ sprintf(msg, "FLIR Lock-On: Locked to point (%.1f, %.1f, %.1f)\n",
+ gLockedWorldX, gLockedWorldY, gLockedWorldZ);
+ XPLMDebugString(msg);
+}
+
+void UpdateCameraToLockPoint(float* outPan, float* outTilt)
+{
+ if (!gLockOnActive || !gPlaneX || !gPlaneY || !gPlaneZ || !gPlaneHeading) {
+ return;
+ }
+
+ // Get current aircraft position
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeY = XPLMGetDataf(gPlaneY);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+ float planeHeading = XPLMGetDataf(gPlaneHeading);
+
+ // Calculate vector from aircraft to locked point
+ float dx = gLockedWorldX - planeX;
+ float dy = gLockedWorldY - planeY;
+ float dz = gLockedWorldZ - planeZ;
+
+ // Calculate distance to locked point
+ float horizontalDist = sqrt(dx * dx + dz * dz);
+ float totalDist = sqrt(dx * dx + dy * dy + dz * dz);
+
+ if (totalDist < 1.0f) {
+ // Too close, maintain last known angles
+ *outPan = gLockOnPan;
+ *outTilt = gLockOnTilt;
+ return;
+ }
+
+ // Calculate required camera angles
+ float targetHeading = atan2(dx, dz) * 180.0f / M_PI;
+ float targetTilt = atan2(dy, horizontalDist) * 180.0f / M_PI;
+
+ // Convert to camera-relative angles
+ *outPan = targetHeading - planeHeading;
+ *outTilt = targetTilt;
+
+ // Normalize pan angle to -180 to +180
+ while (*outPan > 180.0f) *outPan -= 360.0f;
+ while (*outPan < -180.0f) *outPan += 360.0f;
+
+ // Clamp tilt to reasonable limits
+ if (*outTilt > 90.0f) *outTilt = 90.0f;
+ if (*outTilt < -90.0f) *outTilt = -90.0f;
+}
+
+void DisableLockOn()
+{
+ gLockOnActive = 0;
+ XPLMDebugString("FLIR Lock-On: Disabled\n");
+}
+
+int IsLockOnActive()
+{
+ return gLockOnActive;
+}
+
+void GetLockOnStatus(char* statusBuffer, int bufferSize)
+{
+ if (!gLockOnActive) {
+ strncpy(statusBuffer, "LOCK: OFF", bufferSize - 1);
+ statusBuffer[bufferSize - 1] = '\0';
+ return;
+ }
+
+ // Calculate distance to locked point
+ if (gPlaneX && gPlaneY && gPlaneZ) {
+ float planeX = XPLMGetDataf(gPlaneX);
+ float planeY = XPLMGetDataf(gPlaneY);
+ float planeZ = XPLMGetDataf(gPlaneZ);
+
+ float dx = gLockedWorldX - planeX;
+ float dy = gLockedWorldY - planeY;
+ float dz = gLockedWorldZ - planeZ;
+ float distance = sqrt(dx * dx + dy * dy + dz * dz);
+
+ snprintf(statusBuffer, bufferSize, "LOCK: ON %.0fm", distance);
+ } else {
+ strncpy(statusBuffer, "LOCK: ON", bufferSize - 1);
+ }
+ statusBuffer[bufferSize - 1] = '\0';
+}
\ No newline at end of file
diff --git a/FLIR_LockOn.h b/FLIR_LockOn.h
new file mode 100644
index 0000000..c1537ea
--- /dev/null
+++ b/FLIR_LockOn.h
@@ -0,0 +1,36 @@
+/*
+ * FLIR_LockOn.h
+ *
+ * Header file for FLIR camera arbitrary point lock-on system
+ */
+
+#ifndef FLIR_LOCKON_H
+#define FLIR_LOCKON_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Initialize the lock-on system
+void InitializeLockOnSystem();
+
+// Set lock-on point based on current camera direction and distance
+void SetArbitraryLockPoint(float currentPan, float currentTilt, float distance);
+
+// Update camera angles to maintain lock on point
+void UpdateCameraToLockPoint(float* outPan, float* outTilt);
+
+// Disable lock-on
+void DisableLockOn();
+
+// Check if lock-on is active
+int IsLockOnActive();
+
+// Get lock-on status string for display
+void GetLockOnStatus(char* statusBuffer, int bufferSize);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // FLIR_LOCKON_H
\ No newline at end of file
diff --git a/FLIR_LockOn.o b/FLIR_LockOn.o
new file mode 100644
index 0000000..34c3a2b
Binary files /dev/null and b/FLIR_LockOn.o differ
diff --git a/Makefile b/Makefile
index a1dc2e8..77a32a4 100644
--- a/Makefile
+++ b/Makefile
@@ -29,7 +29,7 @@ LDFLAGS += $(LIBS)
LDFLAGS += -lopengl32 -lgdi32
# Source files
-SOURCES = FLIR_Camera.cpp
+SOURCES = FLIR_Camera.cpp FLIR_LockOn.cpp
# Object files
OBJECTS = $(SOURCES:.cpp=.o)
diff --git a/build/FLIR_Camera/win_x64/FLIR_Camera.xpl b/build/FLIR_Camera/win_x64/FLIR_Camera.xpl
index cf8429b..604d4df 100755
Binary files a/build/FLIR_Camera/win_x64/FLIR_Camera.xpl and b/build/FLIR_Camera/win_x64/FLIR_Camera.xpl differ