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