diff --git a/Parts/ThirdPartyParts/rpm-camera-support.cfg b/Parts/ThirdPartyParts/rpm-camera-support.cfg
new file mode 100644
index 0000000..6e735f3
--- /dev/null
+++ b/Parts/ThirdPartyParts/rpm-camera-support.cfg
@@ -0,0 +1,9 @@
+// This patch file adds support for RasterPropMonitor cameras
+
+@PART[JSIPrimitiveExternalCamera]
+{
+ MODULE
+ {
+ name = RasterPropMonitorCamera
+ }
+}
\ No newline at end of file
diff --git a/Telemachus/Telemachus.csproj b/Telemachus/Telemachus.csproj
index 1f9cded..84b9c6c 100644
--- a/Telemachus/Telemachus.csproj
+++ b/Telemachus/Telemachus.csproj
@@ -60,8 +60,16 @@
True
Resources.resx
+
+
+
+
+
+
+
+
diff --git a/Telemachus/src/CameraResponsibility.cs b/Telemachus/src/CameraResponsibility.cs
new file mode 100644
index 0000000..96943c6
--- /dev/null
+++ b/Telemachus/src/CameraResponsibility.cs
@@ -0,0 +1,177 @@
+//Author: Richard Bunt
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Linq;
+using System.Threading;
+using System.Reflection;
+using WebSocketSharp.Net;
+using WebSocketSharp;
+using UnityEngine;
+using System.Collections;
+using Telemachus.CameraSnapshots;
+using System.Text.RegularExpressions;
+
+namespace Telemachus
+{
+ public class CameraResponsibility : IHTTPRequestResponder
+ {
+ /// The page prefix that this class handles
+ public const String PAGE_PREFIX = "/telemachus/cameras";
+ public const String CAMERA_LIST_ENDPOINT = PAGE_PREFIX;
+ public const String NGROK_ORIGINAL_HOST_HEADER = "X-Original-Host";
+ protected Regex _cameraNameEndpointRegex;
+ protected Regex cameraNameEndpointRegex
+ {
+ get
+ {
+ if (_cameraNameEndpointRegex == null)
+ {
+ _cameraNameEndpointRegex = new Regex(Regex.Escape(PAGE_PREFIX) + "\\/(.+)");
+ }
+
+ return _cameraNameEndpointRegex;
+ }
+ }
+
+ /// The KSP API to use to access variable data
+ private IKSPAPI kspAPI = null;
+
+ private UpLinkDownLinkRate dataRates = null;
+
+
+
+ #region Initialisation
+
+ public CameraResponsibility(IKSPAPI kspAPI, UpLinkDownLinkRate rateTracker)
+ {
+ this.kspAPI = kspAPI;
+ dataRates = rateTracker;
+ }
+
+ public void setCameraCapture()
+ {
+ //PluginLogger.debug("START CAMERA CATPURE");
+
+ //PluginLogger.debug("CAM CAMPTURE CREATED");
+ }
+
+ #endregion
+
+ private static Dictionary splitArguments(string argstring)
+ {
+ var ret = new Dictionary();
+ if (argstring.StartsWith("?")) argstring = argstring.Substring(1);
+
+ foreach (var part in argstring.Split('&'))
+ {
+ var subParts = part.Split('=');
+ if (subParts.Length != 2) continue;
+ var keyName = UnityEngine.WWW.UnEscapeURL(subParts[0]);
+ var apiName = UnityEngine.WWW.UnEscapeURL(subParts[1]);
+ ret[keyName] = apiName;
+ }
+ return ret;
+ }
+
+ private static IDictionary parseJSONBody(string jsonBody)
+ {
+ return (IDictionary)SimpleJson.SimpleJson.DeserializeObject(jsonBody);
+ }
+
+ public string cameraURL(HttpListenerRequest request, CameraCapture camera)
+ {
+ String hostname = "";
+ if (request.Headers.Contains(NGROK_ORIGINAL_HOST_HEADER))
+ {
+ hostname = request.Headers[NGROK_ORIGINAL_HOST_HEADER];
+ }
+ else
+ {
+ hostname = request.UserHostName;
+ }
+
+ return request.Url.Scheme + "://" + hostname + PAGE_PREFIX + "/" + UnityEngine.WWW.EscapeURL(camera.cameraManagerName());
+ }
+
+ public bool processCameraManagerIndex(HttpListenerRequest request, HttpListenerResponse response)
+ {
+ if (GameObject.Find("CurrentFlightCameraCapture") == null)
+ {
+ //PluginLogger.debug("REBUILDING CAMERA CAPTURE");
+ this.setCameraCapture();
+ }
+
+ var jsonObject = new List>();
+
+ foreach(KeyValuePair cameraKVP in CameraCaptureManager.classedInstance.cameras)
+ {
+ var jsonData = new Dictionary();
+ jsonData["name"] = cameraKVP.Value.cameraManagerName();
+ jsonData["type"] = cameraKVP.Value.cameraType();
+ jsonData["url"] = cameraURL(request, cameraKVP.Value);
+
+ jsonObject.Add(jsonData);
+ }
+
+ byte[] jsonBytes = Encoding.UTF8.GetBytes(SimpleJson.SimpleJson.SerializeObject(jsonObject));
+
+ response.ContentEncoding = Encoding.UTF8;
+ response.ContentType = "application/json";
+ response.WriteContent(jsonBytes);
+ dataRates.SendDataToClient(jsonBytes.Length);
+
+ return true;
+ }
+
+ public bool processCameraImageRequest(string cameraName, HttpListenerRequest request, HttpListenerResponse response)
+ {
+ cameraName = cameraName.ToLower();
+ if (!CameraCaptureManager.classedInstance.cameras.ContainsKey(cameraName))
+ {
+ response.StatusCode = 404;
+ return true;
+ }
+
+ CameraCapture camera = CameraCaptureManager.classedInstance.cameras[cameraName];
+ //PluginLogger.debug("RENDERING SAVED CAMERA: "+ camera.cameraManagerName());
+ if (camera.didRender)
+ {
+ response.ContentEncoding = Encoding.UTF8;
+ response.ContentType = "image/jpeg";
+ response.WriteContent(camera.imageBytes);
+ dataRates.SendDataToClient(camera.imageBytes.Length);
+ }
+ else
+ {
+ response.StatusCode = 503;
+ }
+
+ return true;
+ }
+
+ public bool process(HttpListenerRequest request, HttpListenerResponse response)
+ {
+ //PluginLogger.debug(request.Url.AbsolutePath.TrimEnd('/'));
+ //PluginLogger.debug(String.Join(",", CameraCaptureManager.classedInstance.cameras.Keys.ToArray()));
+ //PluginLogger.debug("FLIGHT CAMERA: " + this.cameraCaptureTest);
+ if (request.Url.AbsolutePath.TrimEnd('/').ToLower() == CAMERA_LIST_ENDPOINT)
+ {
+ // Work out how big this request was
+ long byteCount = request.RawUrl.Length + request.ContentLength64;
+ // Don't count headers + request.Headers.AllKeys.Sum(x => x.Length + request.Headers[x].Length + 1);
+ dataRates.RecieveDataFromClient(Convert.ToInt32(byteCount));
+
+ return processCameraManagerIndex(request, response);
+ } else if (cameraNameEndpointRegex.IsMatch(request.Url.AbsolutePath))
+ {
+ Match match = cameraNameEndpointRegex.Match(request.Url.AbsolutePath);
+ string cameraName = UnityEngine.WWW.UnEscapeURL(match.Groups[1].Value);
+ //PluginLogger.debug("GET CAMERA: " + cameraName);
+ return processCameraImageRequest(cameraName, request, response);
+ }
+
+ return false;
+ }
+ }
+}
diff --git a/Telemachus/src/CameraSnapshots/CameraCapture.cs b/Telemachus/src/CameraSnapshots/CameraCapture.cs
new file mode 100644
index 0000000..f1b5df5
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/CameraCapture.cs
@@ -0,0 +1,325 @@
+using UnityEngine;
+using System.Collections;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Telemachus.CameraSnapshots
+{
+ public class CameraCapture : MonoBehaviour
+ {
+ public RenderTexture overviewTexture;
+ public bool didRender;
+ public byte[] imageBytes = null;
+ public bool mutex = false;
+ public int renderOffsetFactor = 0;
+
+ public virtual string cameraManagerName()
+ {
+ return "NA";
+ }
+
+ public virtual string cameraType()
+ {
+ return "NA";
+ }
+
+ protected Dictionary cameraDuplicates = new Dictionary();
+ protected List activeCameras = new List();
+ protected static readonly string[] skippedCameras = { "UIMainCamera", "UIVectorCamera", "velocity camera" };
+ private readonly string[] knownCameraNames =
+ {
+ "GalaxyCamera",
+ "Camera ScaledSpace",
+ "Camera VE Underlay", // Environmental Visual Enhancements plugin camera
+ "Camera VE Overlay", // Environmental Visual Enhancements plugin camera
+ "Camera 01",
+ "Camera 00",
+ "InternalCamera",
+ "FXCamera"
+ };
+
+ public Dictionary gameCameraMapping = new Dictionary();
+
+
+ protected string cameraContainerNamePrefix {
+ get
+ {
+ return "TelemachusCameraContainer:" + cameraManagerName();
+ }
+ }
+
+ protected const float fovAngle = 60f;
+ protected const float aspect = 1.0f;
+ public int cameraResolution = 300;
+
+ protected void OnEnable()
+ {
+ Camera.onPostRender += disableCameraIfInList;
+ }
+
+ private void disableCameraIfInList(Camera cam)
+ {
+ if (cameraDuplicates.ContainsValue(cam))
+ {
+ //PluginLogger.debug("DISABLE CAMERA:"+ cam.name);
+ cam.enabled = false;
+ }
+ }
+
+ protected virtual void LateUpdate()
+ {
+ //PluginLogger.debug("LATE UPDATE FOR FLIGHT CAMERA");
+ if (CameraManager.Instance != null && HighLogic.LoadedSceneIsFlight && !mutex)
+ {
+ mutex = true;
+ duplicateAnyNewCameras();
+ repositionCamera();
+ StartCoroutine(newRender());
+ }
+ }
+
+ public IEnumerator newRender()
+ {
+ //PluginLogger.debug(cameraManagerName() + ": WAITING FOR END OF FRAME");
+ yield return new WaitForEndOfFrame();
+ //PluginLogger.debug(cameraManagerName() + ": OUT OF FRAME");
+
+ foreach (Camera camera in cameraDuplicates.Values)
+ {
+ //camera.targetTexture = rt;
+ camera.Render();
+ }
+
+ //imageStopWatch.Start();
+ Texture2D texture = getTexture2DFromRenderTexture();
+ this.imageBytes = texture.EncodeToJPG();
+ this.didRender = true;
+ Destroy(texture);
+ //imageStopWatch.Stop();
+ //PluginLogger.debug(cameraManagerName() + ": TIME TO RENDER: " + imageStopWatch.Elapsed + " : " + DateTime.Now.ToString("hh.mm.ss.ffffff"));
+ //imageStopWatch.Reset();
+ //renderCount++;
+
+ //wait a second before releasing the mutex to improve performance
+ //PluginLogger.debug("RENDER DELAY:" + (1.0f + (.3f * renderOffsetFactor)));
+ yield return new WaitForSeconds(1.0f + (.3f * renderOffsetFactor));
+ mutex = false;
+ }
+
+ public Texture2D getTexture2DFromRenderTexture()
+ {
+ Texture2D texture2D = new Texture2D(overviewTexture.width, overviewTexture.height);
+ RenderTexture.active = overviewTexture;
+ texture2D.ReadPixels(new Rect(0, 0, overviewTexture.width, overviewTexture.height), 0, 0);
+ texture2D.Apply();
+ return texture2D;
+ }
+
+ public void duplicateAnyNewCameras()
+ {
+ if (overviewTexture == null)
+ {
+ overviewTexture = new RenderTexture(cameraResolution, cameraResolution, 24);
+ }
+
+ List currentlyActiveCameras = new List();
+
+ foreach (Camera camera in Camera.allCameras)
+ {
+
+ // Don't duplicate any cameras we're going to skip
+ if (skippedCameras.IndexOf(camera.name) != -1)
+ {
+ continue;
+ }
+
+ // Don't duplicate cameras we don't know about
+ if (knownCameraNames.IndexOf(camera.name) == -1)
+ {
+ continue;
+ }
+
+ //PluginLogger.debug(cameraManagerName() + " {" + verboseCameraDetails(camera) + "}");
+
+ if (!cameraDuplicates.ContainsKey(camera.name))
+ {
+ var cameraDuplicateGameObject = new GameObject(cameraContainerNamePrefix + camera.name);
+ Camera cameraDuplicate = cameraDuplicateGameObject.AddComponent();
+ cameraDuplicates[camera.name] = cameraDuplicate;
+ cameraDuplicate.CopyFrom(camera);
+ cameraDuplicate.fieldOfView = fovAngle;
+ cameraDuplicate.aspect = aspect;
+
+ cameraDuplicate.targetTexture = this.overviewTexture;
+ if (camera.name == "Camera 00" || camera.name == "FXCamera")
+ {
+ cameraDuplicate.nearClipPlane = cameraDuplicate.farClipPlane / 8192.0f;
+ }
+
+ //Now that the camera has been duplicated, add it to the list of active cameras
+ activeCameras.Add(camera.name);
+ if (!gameCameraMapping.ContainsKey(camera.name))
+ {
+ gameCameraMapping[camera.name] = camera;
+ }
+ }
+
+ //Mark the camera as enabled so it will be rendered again
+ if (cameraDuplicates.ContainsKey(camera.name))
+ {
+ cameraDuplicates[camera.name].enabled = false;
+ }
+
+ //Mark that the camera is currently active
+ currentlyActiveCameras.Add(camera.name);
+ }
+
+ if (currentlyActiveCameras.Count > 0 && activeCameras.Count > 0)
+ {
+ IEnumerable disabledCameras = activeCameras.Except(currentlyActiveCameras);
+ foreach (string disabledCamera in disabledCameras)
+ {
+ if (cameraDuplicates.ContainsKey(disabledCamera))
+ {
+ Destroy(cameraDuplicates[disabledCamera]);
+ cameraDuplicates.Remove(disabledCamera);
+ }
+ }
+
+ activeCameras = currentlyActiveCameras;
+ }
+ }
+
+
+ public virtual void repositionCamera()
+ {
+ foreach (KeyValuePair KVP in cameraDuplicates)
+ {
+ Camera cameraDuplicate = KVP.Value;
+ Camera gameCamera = gameCameraMapping[KVP.Key];
+
+ cameraDuplicate.transform.position = gameCamera.transform.position;
+ cameraDuplicate.transform.rotation = gameCamera.transform.rotation;
+
+ additionalCameraUpdates(cameraDuplicate, gameCamera);
+ }
+ }
+
+ public string verboseCameraDetails(Camera camera)
+ {
+ string[] debugProperties = {
+ "CAMERA INFO: " + camera.name,
+ "TARGET DISPLAY: " + camera.targetDisplay,
+ "TARGET TEXTURE: " + camera.targetTexture,
+ "RENDERING PATH: " + camera.renderingPath,
+ "ACTUAL RENDER PATH: " + camera.actualRenderingPath,
+ "CAMERA TYPE: " + camera.cameraType,
+ "GAME OBJECT: " + camera.gameObject,
+ "BG COLOR: " + camera.backgroundColor,
+ "CULLING MASK: " + camera.cullingMask,
+ "DEPTH: " + camera.depth,
+ "HDR: " + camera.hdr,
+ "POSITION: " + camera.transform.position,
+ "ROT: " + camera.transform.rotation,
+ "NEAR: " + camera.nearClipPlane,
+ "FAR: " + camera.farClipPlane,
+ "LOCAL EULER ANGLES: " + camera.transform.localEulerAngles,
+ "LOCAL POSITION: " + camera.transform.localPosition,
+ "LOCAL SCALE: " + camera.transform.localScale,
+ "EULER ANGLES: " + camera.transform.eulerAngles
+ };
+ return String.Join("\n", debugProperties);
+ }
+
+ public void verboseCameraDebug(Camera camera)
+ {
+ PluginLogger.debug(verboseCameraDetails(camera));
+ }
+
+
+ /*public void UpdateCameras()
+ {
+ if (CameraManager.Instance != null)
+ {
+ //PluginLogger.debug("CURRENT CAMERA MODE: " + CameraManager.Instance.currentCameraMode);
+ }
+
+ activeCameras = new List();
+ PluginLogger.debug("UPDATING CAMERAS");
+ foreach (Camera camera in Camera.allCameras)
+ {
+ // debugCameraDetails(camera);
+ // Don't duplicate any cameras we're going to skip
+ if (skippedCameras.IndexOf(camera.name) != -1)
+ {
+ continue;
+ }
+
+ Camera cameraDuplicate;
+
+ if (!cameraDuplicates.ContainsKey(camera.name))
+ {
+ var cameraDuplicateGameObject = new GameObject(cameraContainerNamePrefix + camera.name);
+ cameraDuplicate = cameraDuplicateGameObject.AddComponent();
+ cameraDuplicates[camera.name] = cameraDuplicate;
+ }
+ else
+ {
+ cameraDuplicate = cameraDuplicates[camera.name];
+ }
+
+ cameraDuplicate.CopyFrom(camera);
+ cameraDuplicate.enabled = false;
+ cameraDuplicate.fieldOfView = fovAngle;
+ cameraDuplicate.aspect = aspect;
+
+ if (camera.name == "Camera 00" || camera.name == "FXCamera")
+ {
+ //PluginLogger.debug("ADJUSTING NEAR CLIPPING PLANE FOR: " + camera.name + " : " + cameraDuplicate.farClipPlane / 8192.0f);
+ cameraDuplicate.nearClipPlane = cameraDuplicate.farClipPlane / 8192.0f;
+ }
+
+ additionalCameraUpdates(cameraDuplicate);
+
+ //Now that the camera has been duplicated, add it to the list of active cameras
+ activeCameras.Add(camera.name);
+ }
+ }*/
+
+ public virtual void additionalCameraUpdates(Camera dupliateCam, Camera gameCamera){ }
+
+ public virtual void debugCameraDetails(Camera cam)
+ {
+ PluginLogger.debug("CAMERA: " + cam.name + " POS: " + cam.transform.position + "; ROT: " + cam.transform.rotation + " ; NEAR:" + cam.nearClipPlane + "; FAR: " + cam.farClipPlane);
+ }
+
+ public virtual void BeforeRenderNewScreenshot(){ }
+
+ /*public IEnumerator NewScreenshot()
+ {
+ if (mutex)
+ {
+ yield return true;
+ }
+ //PluginLogger.debug("BYPASSED MUTEX");
+ mutex = true;
+ //PluginLogger.debug("WAITING FOR END OF FRAME");
+ yield return new WaitForEndOfFrame();
+
+ BeforeRenderNewScreenshot();
+
+ List renderingCameras = new List();
+ foreach (string cameraName in activeCameras)
+ {
+ //PluginLogger.debug("[" + cameraManagerName() + "] GETTING CAMERA " + cameraName);
+ renderingCameras.Add(cameraDuplicates[cameraName]);
+ }
+
+ this.imageBytes = SnapshotRenderer.renderSnaphot(renderingCameras, cameraResolution, cameraResolution);
+ this.didRender = true;
+ mutex = false;
+ }*/
+
+ }
+}
\ No newline at end of file
diff --git a/Telemachus/src/CameraSnapshots/CameraCaptureManager.cs b/Telemachus/src/CameraSnapshots/CameraCaptureManager.cs
new file mode 100644
index 0000000..5479271
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/CameraCaptureManager.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+
+namespace Telemachus.CameraSnapshots
+{
+ class CameraCaptureManager : MonoBehaviour
+ {
+ #region Singleton management
+ public static GameObject instance;
+
+ private CameraCaptureManager() { }
+
+ public static GameObject Instance
+ {
+ get
+ {
+ if (CameraCaptureManager.instance == null)
+ {
+ instance = GameObject.Find("CameraCaptureManager")
+ ?? new GameObject("CameraCaptureManager", typeof(CameraCaptureManager));
+ }
+
+ return instance;
+ }
+ }
+
+ public static CameraCaptureManager classedInstance
+ {
+ get {
+ return (CameraCaptureManager)Instance.GetComponent(typeof(CameraCaptureManager));
+ }
+ }
+ #endregion
+
+ private CurrentFlightCameraCapture cameraCaptureTest = null;
+ public Dictionary cameras = new Dictionary();
+ public Dictionary> vesselCameraMappings = new Dictionary>();
+
+
+ public void addToVesselCameraMappings(Vessel vessel, string cameraName)
+ {
+ List vesselList;
+ if (!vesselCameraMappings.ContainsKey(vessel.id))
+ {
+ vesselList = new List();
+ vesselCameraMappings[vessel.id] = vesselList;
+ }
+ else
+ {
+ vesselList = vesselCameraMappings[vessel.id];
+ }
+
+ if (!vesselList.Contains(cameraName))
+ {
+ //PluginLogger.debug("ADDING: " + cameraName + " TO : " + vessel.id);
+ vesselList.Add(cameraName);
+ }
+ }
+
+ protected void OnEnable()
+ {
+ GameEvents.onFlightReady.Add(addFlightCamera);
+ GameEvents.onGameSceneLoadRequested.Add(removeFlightCameraIfNotFlight);
+ }
+
+ private void removeFlightCameraIfNotFlight(GameScenes data)
+ {
+ if(data != GameScenes.FLIGHT && cameraCaptureTest)
+ {
+ removeCamera(cameraCaptureTest.cameraManagerName());
+ Destroy(cameraCaptureTest.gameObject);
+ cameraCaptureTest = null;
+ }
+ }
+
+ private void addFlightCamera()
+ {
+ GameObject obj = new GameObject("CurrentFlightCameraCapture", typeof(CurrentFlightCameraCapture));
+ this.cameraCaptureTest = (CurrentFlightCameraCapture)obj.GetComponent(typeof(CurrentFlightCameraCapture));
+ addCameraCapture(cameraCaptureTest);
+ }
+
+ public bool isRemoveCameraFromManager(Vessel vessel, string name)
+ {
+ //PluginLogger.debug("CHECKING FOR: " + name + " IN : " + vessel.id);
+ if (!vesselCameraMappings.ContainsKey(vessel.id)){
+ return true;
+ }
+
+ //PluginLogger.debug("FOUND KEY: " + vessel.id);
+
+
+ if (!vesselCameraMappings[vessel.id].Contains(name))
+ {
+ //PluginLogger.debug("MISSING: " + name + " IN : " + vessel.id);
+ return true;
+ }
+
+ return false;
+ }
+
+ public void addCamera(RasterPropMonitorCamera camera)
+ {
+ if(camera == null)
+ {
+ return;
+ }
+
+ GameObject container = new GameObject("RasterPropMonitorCameraCapture:" + camera.cameraName, typeof(RasterPropMonitorCameraCapture));
+ RasterPropMonitorCameraCapture cameraCapture = (RasterPropMonitorCameraCapture)container.GetComponent(typeof(RasterPropMonitorCameraCapture));
+ cameraCapture.rpmCamera = camera;
+
+ string name = cameraCapture.cameraManagerName().ToLower();
+ cameras[name] = cameraCapture;
+ cameraCapture.renderOffsetFactor = cameras.Count;
+ addToVesselCameraMappings(camera.vessel, camera.cameraName);
+ }
+
+ public void addCameraCapture(CameraCapture cameraCapture)
+ {
+ cameras[cameraCapture.cameraManagerName().ToLower()] = cameraCapture;
+ cameraCapture.renderOffsetFactor = cameras.Count;
+ }
+
+ public void removeCamera(string name)
+ {
+ cameras.Remove(name.ToLower());
+ }
+ }
+}
diff --git a/Telemachus/src/CameraSnapshots/CurrentFlightCameraCapture.cs b/Telemachus/src/CameraSnapshots/CurrentFlightCameraCapture.cs
new file mode 100644
index 0000000..0986e14
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/CurrentFlightCameraCapture.cs
@@ -0,0 +1,31 @@
+using UnityEngine;
+using System.Collections;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace Telemachus.CameraSnapshots
+{
+ public class CurrentFlightCameraCapture : CameraCapture
+ {
+ public override string cameraManagerName()
+ {
+ return "TelemachusFlightCamera";
+ }
+
+ public override string cameraType()
+ {
+ return "FlightCamera";
+ }
+
+
+
+ public override void BeforeRenderNewScreenshot()
+ {
+ //UpdateCameras();
+ //base.BeforeRenderNewScreenshot();
+ }
+
+
+ }
+}
\ No newline at end of file
diff --git a/Telemachus/src/CameraSnapshots/IVACameraCapture.cs b/Telemachus/src/CameraSnapshots/IVACameraCapture.cs
new file mode 100644
index 0000000..1f38c00
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/IVACameraCapture.cs
@@ -0,0 +1,135 @@
+using UnityEngine;
+using System.Collections;
+using System;
+using System.Collections.Generic;
+
+namespace Telemachus.CameraSnapshots
+{
+ public class IVACameraCapture : MonoBehaviour
+ {
+ public RenderTexture overviewTexture;
+ public bool didRender;
+ public byte[] imageBytes = null;
+ public bool mutex = false;
+
+ protected Dictionary cameraDuplicates = new Dictionary();
+ protected List activeCameras;
+ protected static readonly string[] skippedCameras = { "UIMainCamera", "UIVectorCamera" };
+ protected static string cameraContainerNamePrefix = "Telemachus Camera Container - ";
+
+ protected float fovAngle = 60f;
+
+ private const float defaultFovAngle = 60f;
+ private const float aspect = 1.0f;
+ private int camerares = 300;
+
+ void LateUpdate()
+ {
+ PluginLogger.debug("LATEUPDATE FOR CAMERA");
+ if(InternalCamera.Instance != null)
+ {
+ PluginLogger.debug("INTERNAL CAMERA Position: " + InternalCamera.Instance.transform.position.ToString());
+ PluginLogger.debug("INTERNAL CAMERA Rotation: " + InternalCamera.Instance.transform.rotation.ToString());
+
+ if (CameraManager.Instance.currentCameraMode == CameraManager.CameraMode.IVA)
+ {
+ foreach(Camera camera in Camera.allCameras)
+ {
+ if(camera.name == "InternalCamera")
+ {
+ fovAngle = camera.fieldOfView;
+ }
+ }
+ }
+ else
+ {
+ fovAngle = defaultFovAngle;
+ }
+
+ PluginLogger.debug("INTERNAL CAMERA Zoom: " + fovAngle);
+ }
+ else
+ {
+ PluginLogger.debug("NO INTERNAL CAMERA");
+ }
+
+ if(CameraManager.Instance != null)
+ {
+ PluginLogger.debug("CAMERA FOUND, TAKING SCREENSHOT");
+ StartCoroutine(NewScreenshot());
+ }
+ }
+
+ public void UpdateCameras()
+ {
+ if (CameraManager.Instance != null)
+ {
+ PluginLogger.debug("CURRENT CAMERA MODE: " + CameraManager.Instance.currentCameraMode);
+ }
+
+ activeCameras = new List();
+
+ foreach (Camera camera in Camera.allCameras)
+ {
+ debugCameraDetails(camera);
+ // Don't duplicate any cameras we're going to skip
+ if (skippedCameras.IndexOf(camera.name) != -1)
+ {
+ continue;
+ }
+
+ Camera cameraDuplicate;
+
+ if (!cameraDuplicates.ContainsKey(camera.name))
+ {
+ var cameraDuplicateGameObject = new GameObject(cameraContainerNamePrefix + camera.name);
+ cameraDuplicate = cameraDuplicateGameObject.AddComponent();
+ cameraDuplicates[camera.name] = cameraDuplicate;
+ }
+ else
+ {
+ cameraDuplicate = cameraDuplicates[camera.name];
+ }
+
+ cameraDuplicate.CopyFrom(camera);
+ cameraDuplicate.enabled = false;
+ cameraDuplicate.fieldOfView = fovAngle;
+ cameraDuplicate.aspect = aspect;
+
+ //Now that the camera has been duplicated, add it to the list of active cameras
+ activeCameras.Add(camera.name);
+ }
+ }
+
+ public void debugCameraDetails(Camera cam)
+ {
+ PluginLogger.debug("CAMERA: " + cam.name + " ; NEAR CLIP PLANE: "+ cam.nearClipPlane +"; FAR CLIP PLANE: " + cam.farClipPlane + " FOV : " + cam.fieldOfView + " POSITION: " + cam.transform.position.ToString() + " ROTATION: " + cam.transform.rotation.ToString());
+ }
+
+ public IEnumerator NewScreenshot()
+ {
+ if (mutex)
+ {
+ yield return true;
+ }
+ PluginLogger.debug("BYPASSED MUTEX");
+ mutex = true;
+ PluginLogger.debug("WAITING FOR END OF FRAME");
+ yield return new WaitForEndOfFrame();
+
+ UpdateCameras();
+
+ List renderingCameras = new List();
+ foreach (string cameraName in activeCameras)
+ {
+ PluginLogger.debug("GETTING CAMERA" + cameraName);
+ renderingCameras.Add(cameraDuplicates[cameraName]);
+ }
+
+ this.imageBytes = SnapshotRenderer.renderSnaphot(renderingCameras, camerares, camerares);
+ this.didRender = true;
+ mutex = false;
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Telemachus/src/CameraSnapshots/RasterPropMonitorCamera.cs b/Telemachus/src/CameraSnapshots/RasterPropMonitorCamera.cs
new file mode 100644
index 0000000..403ffc7
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/RasterPropMonitorCamera.cs
@@ -0,0 +1,201 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace Telemachus.CameraSnapshots
+{
+ class RasterPropMonitorCamera : PartModule
+ {
+ protected PartModule rpmPartModule;
+ public PartModule rpmCameraModule
+ {
+ get
+ {
+ if (rpmPartModule == null)
+ {
+ foreach (PartModule module in part.Modules)
+ {
+ if (module.moduleName == "JSIExternalCameraSelector")
+ {
+ //PluginLogger.debug("GOT MODULE");
+ rpmPartModule = module;
+ }
+ }
+ }
+
+ return rpmPartModule;
+ }
+ }
+
+ protected string rpmCameraName;
+ public string cameraName
+ {
+ get
+ {
+ if(rpmCameraName == null)
+ {
+ if (rpmCameraModule != null)
+ {
+ rpmCameraName = (String)getRPMField("cameraIDPrefix") + (int)getRPMField("current");
+ }
+ }
+
+ return rpmCameraName;
+ }
+ }
+
+ protected UnityEngine.Vector3 rpmRotateCamera;
+ public UnityEngine.Vector3 rotateCamera
+ {
+ get
+ {
+ if (rpmRotateCamera == UnityEngine.Vector3.zero)
+ {
+ if (rpmCameraModule != null)
+ {
+ rpmRotateCamera = (UnityEngine.Vector3)getRPMField("rotateCamera");
+ }
+ }
+
+ return rpmRotateCamera;
+ }
+ }
+
+ public UnityEngine.Vector3 cameraRotation()
+ {
+ return (UnityEngine.Vector3)getRPMField("rotateCamera");
+ }
+
+ protected UnityEngine.Vector3 rpmTranslateCamera;
+ public UnityEngine.Vector3 translateCamera
+ {
+ get
+ {
+ if (rpmTranslateCamera == UnityEngine.Vector3.zero)
+ {
+ if (rpmCameraModule != null)
+ {
+ rpmTranslateCamera = (UnityEngine.Vector3)getRPMField("translateCamera");
+ }
+ }
+
+ return rpmTranslateCamera;
+ }
+ }
+
+ public override void OnStart(PartModule.StartState state)
+ {
+ if (FlightGlobals.fetch != null)
+ {
+ GameEvents.onVesselChange.Add(updateCameraManager);
+ if (vessel == FlightGlobals.ActiveVessel)
+ {
+ DebugInfo();
+ addToManager();
+ }
+ }
+ }
+
+ private void updateCameraManager(Vessel data)
+ {
+ if(data == vessel)
+ {
+ addToManager();
+ }
+ else
+ {
+ if (CameraCaptureManager.classedInstance.isRemoveCameraFromManager(data, this.cameraName))
+ {
+ //PluginLogger.debug("REMOVING CAMERA: " + RasterPropMonitorCameraCapture.buildCameraManagerName(this.cameraName));
+ removeFromManager();
+ }
+ }
+ }
+
+ protected void addToManager()
+ {
+ CameraCaptureManager.Instance.BroadcastMessage("addCamera", this);
+ }
+
+ protected void removeFromManager()
+ {
+ CameraCaptureManager.Instance.BroadcastMessage("removeCamera", RasterPropMonitorCameraCapture.buildCameraManagerName(this.cameraName));
+ }
+
+ public void OnDestroy()
+ {
+ removeFromManager();
+ }
+
+ public void Update()
+ {
+ //DebugInfo();
+ }
+
+ // DEBUGGING
+
+ public object getRPMField(string name)
+ {
+ if (rpmCameraModule == null)
+ {
+ return null;
+ }
+
+ return rpmCameraModule.Fields.GetValue(name);
+ }
+
+ public string fieldValues()
+ {
+ string val = "";
+ foreach (BaseField field in Fields)
+ {
+ val += field.name + " : " + field.originalValue + " || ";
+ }
+
+ return val;
+ }
+
+ public string partModules()
+ {
+ string val = "";
+ foreach (PartModule module in part.Modules)
+ {
+ val += module.moduleName + " ||";
+ }
+
+ return val;
+ }
+
+ public string debugRPMFields()
+ {
+ string val = "";
+ PartModule rpmModule = null;
+
+ foreach (PartModule module in part.Modules)
+ {
+ if (module.moduleName == "JSIExternalCameraSelector")
+ {
+ rpmModule = module;
+ }
+ }
+
+ if (rpmCameraModule == null)
+ {
+ return "NA";
+ }
+
+ foreach (BaseField field in rpmCameraModule.Fields)
+ {
+ val += field.name + " : " + field.originalValue + " || ";
+ }
+
+ return val;
+ }
+
+ public void DebugInfo()
+ {
+ PluginLogger.debug("RPM CAMERA LOADED: " + part.name + " ; NAME: " + cameraName + " ; POS: " + part.transform.position + "; ROTATION: " + rotateCamera + " ; TRANSLATE: " + translateCamera);
+ }
+ }
+}
diff --git a/Telemachus/src/CameraSnapshots/RasterPropMonitorCameraCapture.cs b/Telemachus/src/CameraSnapshots/RasterPropMonitorCameraCapture.cs
new file mode 100644
index 0000000..f36db01
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/RasterPropMonitorCameraCapture.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using UnityEngine;
+
+namespace Telemachus.CameraSnapshots
+{
+ class RasterPropMonitorCameraCapture : CameraCapture
+ {
+ public RasterPropMonitorCamera rpmCamera;
+ protected static string cameraManagerNamePrefix = "RPMCamera-";
+ protected static readonly string[] camerasToSkipPositionTransform = { "GalaxyCamera", "Camera ScaledSpace", "Camera VE Underlay" };
+ protected Regex _cameraSkipRegex;
+ protected Regex cameraSkipRegex
+ {
+ get
+ {
+ if(_cameraSkipRegex == null)
+ {
+ _cameraSkipRegex = new Regex("(" + String.Join("|", camerasToSkipPositionTransform) + ")$");
+ }
+
+ return _cameraSkipRegex;
+ }
+ }
+
+ public override string cameraManagerName()
+ {
+ return buildCameraManagerName(rpmCamera.cameraName);
+ }
+
+ public override string cameraType()
+ {
+ return "RasterPropMonitor";
+ }
+
+ protected bool builtCameraDuplicates = false;
+
+ public static string buildCameraManagerName(string name)
+ {
+ return cameraManagerNamePrefix + name;
+ }
+
+ public override void additionalCameraUpdates(Camera dupliateCam, Camera gameCamera)
+ {
+ base.additionalCameraUpdates(dupliateCam, gameCamera);
+ if (!cameraSkipRegex.IsMatch(gameCamera.name))
+ {
+ dupliateCam.transform.position = rpmCamera.part.transform.position;
+ }
+
+ // Just in case to support JSITransparentPod.
+ //cam.cullingMask &= ~(1 << 16 | 1 << 20);
+
+ dupliateCam.transform.rotation = rpmCamera.part.transform.rotation;
+ dupliateCam.transform.Rotate(rpmCamera.rotateCamera);
+ dupliateCam.transform.position += rpmCamera.translateCamera;
+ }
+
+ /*public override void additionalCameraUpdates(Camera cam)
+ {
+ if (!cameraSkipRegex.IsMatch(cam.name))
+ {
+ cam.transform.position = rpmCamera.part.transform.position;
+ }
+
+ // Just in case to support JSITransparentPod.
+ //cam.cullingMask &= ~(1 << 16 | 1 << 20);
+
+ cam.transform.rotation = rpmCamera.part.transform.rotation;
+ cam.transform.Rotate(rpmCamera.rotateCamera);
+ cam.transform.position += rpmCamera.translateCamera;
+
+ base.additionalCameraUpdates(cam);
+ }*/
+ }
+}
diff --git a/Telemachus/src/CameraSnapshots/SnapshotRenderer.cs b/Telemachus/src/CameraSnapshots/SnapshotRenderer.cs
new file mode 100644
index 0000000..eae6855
--- /dev/null
+++ b/Telemachus/src/CameraSnapshots/SnapshotRenderer.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using System.Collections;
+
+namespace Telemachus.CameraSnapshots
+{
+ class SnapshotRenderer : MonoBehaviour
+ {
+ public static byte[] renderSnaphot(List cameras, int width, int height)
+ {
+ PluginLogger.debug("RENDERING SNAPSHOT");
+ /*RenderTexture rt = new RenderTexture(width, height, 24);
+
+ foreach(Camera camera in cameras) {
+ camera.targetTexture = rt;
+ //camera.Render();
+ } */
+
+ Texture2D screenShot = new Texture2D(width, height, TextureFormat.RGB24, false);
+ //RenderTexture backupRenderTexture = RenderTexture.active;
+ //RenderTexture.active = rt;
+ screenShot.ReadPixels(new Rect(0, 0, width, height), 0, 0);
+
+ foreach (Camera camera in cameras)
+ {
+ camera.targetTexture = null;
+ }
+
+ //RenderTexture.active = backupRenderTexture;
+
+ byte[] result = screenShot.EncodeToJPG();
+ Destroy(screenShot);
+ //Destroy(rt);
+
+ return result;
+ }
+ }
+}
diff --git a/Telemachus/src/IOPageResponsibility.cs b/Telemachus/src/IOPageResponsibility.cs
index 8e2cc17..cbb1b98 100644
--- a/Telemachus/src/IOPageResponsibility.cs
+++ b/Telemachus/src/IOPageResponsibility.cs
@@ -86,6 +86,9 @@ private HTMLResponseContentType GetContentType(string extension)
contentTypes[".ttf"] = new HTMLResponseContentType { contentType = HTMLContentType.BinaryContent, mimeType = "application/font-sfnt" };
contentTypes[".woff"] = new HTMLResponseContentType { contentType = HTMLContentType.BinaryContent, mimeType = "application/font-woff" };
contentTypes[".otf"] = new HTMLResponseContentType { contentType = HTMLContentType.BinaryContent, mimeType = "application/font-sfnt" };
+ contentTypes[".mp4"] = new HTMLResponseContentType { contentType = HTMLContentType.BinaryContent, mimeType = "video/mp4" };
+ contentTypes[".json"] = new HTMLResponseContentType { contentType = HTMLContentType.TextContent, mimeType = "application/json" };
+ contentTypes[".txt"] = new HTMLResponseContentType { contentType = HTMLContentType.TextContent, mimeType = "text/plain" };
contentTypes[""] = new HTMLResponseContentType { contentType = HTMLContentType.BinaryContent, mimeType = null };
}
diff --git a/Telemachus/src/TelemachusBehaviour.cs b/Telemachus/src/TelemachusBehaviour.cs
index c6f2f72..27e0667 100644
--- a/Telemachus/src/TelemachusBehaviour.cs
+++ b/Telemachus/src/TelemachusBehaviour.cs
@@ -67,6 +67,8 @@ static private void startDataLink()
webDispatcher = new KSPWebServerDispatcher();
webDispatcher.AddResponder(new ElseResponsibility());
webDispatcher.AddResponder(new IOPageResponsibility());
+ var cameraLink = new CameraResponsibility(apiInstance, rateTracker);
+ webDispatcher.AddResponder(cameraLink);
var dataLink = new DataLinkResponsibility(apiInstance, rateTracker);
webDispatcher.AddResponder(dataLink);
diff --git a/dependencies/ModuleManager.2.6.25.dll b/dependencies/ModuleManager.2.6.25.dll
new file mode 100644
index 0000000..23ae57f
Binary files /dev/null and b/dependencies/ModuleManager.2.6.25.dll differ