public void Start()
        {
            // If we're not in the correct location, there's no point doing anything.
            if (!InstallationPathWarning.Warn())
            {
                return;
            }

            if (HighLogic.LoadedSceneIsEditor)
            {
                return;
            }

            try
            {
                // Install the calculator module.
                RPMVesselComputer comp = RPMVesselComputer.Instance(vessel);
                comp.UpdateDataRefreshRate(refreshDataRate);

                persistence = new PersistenceAccessor(internalProp);

                // Loading the font...
                List <Texture2D> fontTexture = new List <Texture2D>();
                fontTexture.Add(LoadFont(this, internalProp, fontTransform));

                // Damn KSP's config parser!!!
                if (!string.IsNullOrEmpty(emptyColor))
                {
                    emptyColorValue = ConfigNode.ParseColor32(emptyColor);
                }
                if (!string.IsNullOrEmpty(defaultFontTint))
                {
                    defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint);
                }

                if (!string.IsNullOrEmpty(fontDefinition))
                {
                    JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition);
                    fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0];
                }

                // Now that is done, proceed to setting up the screen.

                screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32);
                screenMat     = internalProp.FindModelTransform(screenTransform).renderer.material;
                foreach (string layerID in textureLayerID.Split())
                {
                    screenMat.SetTexture(layerID.Trim(), screenTexture);
                }

                if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes()))
                {
                    noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false);
                }

                // Create camera instance...
                cameraStructure = new FlyingCamera(part, screenTexture, cameraAspect);

                // The neat trick. IConfigNode doesn't work. No amount of kicking got it to work.
                // Well, we don't need it. GameDatabase, gimme config nodes for all props!
                foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
                {
                    // Now, we know our own prop name.
                    if (node.GetValue("name") == internalProp.propName)
                    {
                        // So this is the configuration of our prop in memory. Nice place.
                        // We know it contains at least one MODULE node, us.
                        // And we know our moduleID, which is the number in order of being listed in the prop.
                        // Therefore the module by that number is our module's own config node.

                        ConfigNode   moduleConfig = node.GetNodes("MODULE")[moduleID];
                        ConfigNode[] pageNodes    = moduleConfig.GetNodes("PAGE");

                        // Which we can now parse for page definitions.
                        for (int i = 0; i < pageNodes.Length; i++)
                        {
                            // Mwahahaha.
                            try
                            {
                                var newPage = new MonitorPage(i, pageNodes[i], this);
                                activePage = activePage ?? newPage;
                                if (newPage.isDefault)
                                {
                                    activePage = newPage;
                                }
                                pages.Add(newPage);
                            }
                            catch (ArgumentException e)
                            {
                                JUtil.LogMessage(this, "Warning - {0}", e);
                            }
                        }

                        // Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts.
                        foreach (string value in moduleConfig.GetValues("extraFont"))
                        {
                            fontTexture.Add(LoadFont(this, internalProp, value));
                        }

                        break;
                    }
                }
                JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count);

                textRenderer = new TextRenderer(fontTexture, new Vector2((float)fontLetterWidth, (float)fontLetterHeight), fontDefinitionString, 17, screenPixelWidth, screenPixelHeight);

                // Load our state from storage...
                persistentVarName = "activePage" + internalProp.propID;
                int activePageID = persistence.GetVar(persistentVarName, pages.Count);
                if (activePageID < pages.Count)
                {
                    activePage = pages[activePageID];
                }
                activePage.Active(true);

                // If we have global buttons, set them up.
                if (!string.IsNullOrEmpty(globalButtons))
                {
                    string[] tokens = globalButtons.Split(',');
                    for (int i = 0; i < tokens.Length; i++)
                    {
                        string buttonName = tokens[i].Trim();
                        // Notice that holes in the global button list ARE legal.
                        if (!string.IsNullOrEmpty(buttonName))
                        {
                            SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease);
                        }
                    }
                }

                audioOutput = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false);

                // One last thing to make sure of: If our pod is transparent, we're always active.
                ourPodIsTransparent = JUtil.IsPodTransparent(part);

                // And if the try block never completed, startupComplete will never be true.
                startupComplete = true;
            }
            catch
            {
                JUtil.AnnoyUser(this);
                startupFailed = true;
                // We can also disable ourselves, that should help.
                enabled = false;
                // And now that we notified the user that config is borked, we rethrow the exception so that
                // it gets logged and we can debug.
                throw;
            }
        }
Esempio n. 2
0
        public bool RenderCamera(RenderTexture screen, float cameraAspect)
        {
            // Just in case.
            if (!HighLogic.LoadedSceneIsFlight)
            {
                return(false);
            }

            if (cameras.Count < 1)
            {
                return(false);
            }

            var activeCamera = cameras[currentCamera];

            if (string.IsNullOrEmpty(activeCamera.cameraTransform))
            {
                return(false);
            }

            if (cameraObject == null)
            {
                cameraObject = new FlyingCamera(part, screen, cameraAspect);
                cameraObject.PointCamera(activeCamera.cameraTransform, activeCamera.currentFoV);
            }

            cameraObject.FOV = activeCamera.currentFoV;

            // Negate pitch - the camera object treats a negative pitch as "up"
            if (cameraObject.Render(activeCamera.currentYaw, -activeCamera.currentPitch))
            {
                ITargetable target = FlightGlobals.fetch.VesselTarget;

                bool drawSomething = ((gizmoTexture != null && target != null && showTargetIcon) || homeCrosshairMaterial.color.a > 0);

                if (drawSomething)
                {
                    GL.PushMatrix();
                    GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
                }

                if (gizmoTexture != null && target != null && showTargetIcon)
                {
                    // Figure out which direction the target is.
                    Vector3 targetDisplacement = target.GetTransform().position - cameraObject.GetTransform().position;
                    targetDisplacement.Normalize();

                    // Transform it using the active camera's rotation.
                    var targetDisp = GetNormalizedScreenPosition(activeCamera, targetDisplacement, cameraAspect);

                    var iconCenter = new Vector2(screen.width * targetDisp.x, screen.height * targetDisp.y);

                    // Apply some clamping values to force the icon to stay on screen
                    iconCenter.x = Math.Max(iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.y = Math.Max(iconPixelSize * 0.5f, iconCenter.y);
                    iconCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, iconCenter.y);

                    var position = new Rect(iconCenter.x - iconPixelSize * 0.5f, iconCenter.y - iconPixelSize * 0.5f, iconPixelSize, iconPixelSize);

                    Graphics.DrawTexture(position, gizmoTexture, GizmoIcons.GetIconLocation(GizmoIcons.IconType.TARGETPLUS), 0, 0, 0, 0, iconMaterial);
                }

                if (homeCrosshairMaterial.color.a > 0)
                {
                    // Mihara: Reference point cameras are different enough to warrant it.
                    var cameraForward   = cameraObject.GetTransformForward();
                    var crossHairCenter = GetNormalizedScreenPosition(activeCamera, cameraForward, cameraAspect);
                    crossHairCenter.x *= screen.width;
                    crossHairCenter.y *= screen.height;
                    crossHairCenter.x  = Math.Max(iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.x  = Math.Min(screen.width - iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.y  = Math.Max(iconPixelSize * 0.5f, crossHairCenter.y);
                    crossHairCenter.y  = Math.Min(screen.height - iconPixelSize * 0.5f, crossHairCenter.y);

                    float zoomAdjustedIconSize = iconPixelSize * Mathf.Tan(Mathf.Deg2Rad * activeCamera.fovLimits.y * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * activeCamera.currentFoV * 0.5f);

                    homeCrosshairMaterial.SetPass(0);
                    GL.Begin(GL.LINES);
                    GL.Vertex3(crossHairCenter.x - zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x + zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y - zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y + zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.End();
                }

                if (drawSomething)
                {
                    GL.PopMatrix();
                }

                return(true);
            }
            return(false);
        }
        /// <summary>
        /// Initialize the renderable game objects for the HUD.
        /// </summary>
        /// <param name="screenWidth"></param>
        /// <param name="screenHeight"></param>
        void InitializeRenderables(RenderTexture screen)
        {
            float screenWidth = (float)screen.width;
            float screenHeight = (float)screen.height;

            Shader displayShader = JUtil.LoadInternalShader("RPM-DisplayShader");

            if (!string.IsNullOrEmpty(cameraTransform))
            {
                cameraObject = new FlyingCamera(part, screen, hudCamera.aspect);
                cameraObject.PointCamera(cameraTransform, hudFov);
            }

            if (!string.IsNullOrEmpty(staticOverlay))
            {
                Material overlayMaterial = new Material(displayShader);
                overlayMaterial.color = Color.white;
                Texture overlayTexture = GameDatabase.Instance.GetTexture(staticOverlay.EnforceSlashes(), false);
                overlayMaterial.mainTexture = overlayTexture;

                overlayMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayOverlay" + hudCamera.GetInstanceID(), screenWidth * 0.5f, drawingLayer);
                overlayMesh.transform.position = new Vector3(0, 0, 1.0f);
                overlayMesh.renderer.material = overlayMaterial;
                overlayMesh.transform.parent = cameraBody.transform;

                JUtil.ShowHide(false, overlayMesh);
            }

            if (!string.IsNullOrEmpty(horizonTexture))
            {
                Shader ladderShader = JUtil.LoadInternalShader("RPM-CroppedDisplayShader");
                Material ladderMaterial = new Material(ladderShader);

                // _CropBound is in device normalized coordinates (-1 - +1)
                Vector4 cropBound = new Vector4(-horizonSize.x / screenWidth, -horizonSize.y / screenHeight, horizonSize.x / screenWidth, horizonSize.y / screenHeight);
                ladderMaterial.SetVector("_CropBound", cropBound);
                ladderMaterial.color = Color.white;
                ladderMaterial.mainTexture = GameDatabase.Instance.GetTexture(horizonTexture.EnforceSlashes(), false);
                if (ladderMaterial.mainTexture != null)
                {
                    horizonTextureSize.x = 0.5f * (horizonTextureSize.x / ladderMaterial.mainTexture.width);
                    horizonTextureSize.y = 0.5f * (horizonTextureSize.y / ladderMaterial.mainTexture.height);

                    ladderMaterial.mainTexture.wrapMode = TextureWrapMode.Clamp;

                    ladderMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayLadder" + hudCamera.GetInstanceID(), new Vector2(horizonSize.x * 0.5f, horizonSize.y * 0.5f), new Rect(0.0f, 0.0f, 1.0f, 1.0f), drawingLayer);
                    ladderMesh.transform.position = new Vector3(0, 0, 1.4f);
                    ladderMesh.renderer.material = ladderMaterial;
                    ladderMesh.transform.parent = cameraBody.transform;

                    JUtil.ShowHide(false, ladderMesh);

                    if (progradeColorValue.a > 0.0f && showLadderPrograde)
                    {
                        Material progradeIconMaterial = new Material(displayShader);
                        progradeIconMaterial.color = Color.white;
                        Rect texCoord;
                        if (string.IsNullOrEmpty(ladderProgradeTexture))
                        {
                            progradeIconMaterial.mainTexture = JUtil.GetGizmoTexture();
                            texCoord = GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE);
                        }
                        else
                        {
                            Texture2D progradeTexture = GameDatabase.Instance.GetTexture(ladderProgradeTexture.EnforceSlashes(), false);
                            if (progradeTexture == null)
                            {
                                JUtil.LogErrorMessage(this, "Failed to find ladder prograde texture \"{0}\".", ladderProgradeTexture);
                            }
                            progradeIconMaterial.mainTexture = progradeTexture;
                            texCoord = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
                        }
                        progradeIconMaterial.SetVector("_Color", progradeColorValue);

                        progradeLadderIcon = JUtil.CreateSimplePlane("JSIHeadsUpDisplayLadderProgradeIcon" + hudCamera.GetInstanceID(), new Vector2(iconPixelSize * 0.5f, iconPixelSize * 0.5f), texCoord, drawingLayer);
                        progradeLadderIcon.transform.position = new Vector3(0.0f, 0.0f, 1.35f);
                        progradeLadderIcon.renderer.material = progradeIconMaterial;
                        progradeLadderIcon.transform.parent = cameraBody.transform;
                    }
                }
            }

            if (!string.IsNullOrEmpty(headingBar))
            {
                Material headingMaterial = new Material(displayShader);
                headingMaterial.color = Color.white;
                headingMaterial.mainTexture = GameDatabase.Instance.GetTexture(headingBar.EnforceSlashes(), false);
                if (headingMaterial.mainTexture != null)
                {
                    headingBarTextureWidth = 0.5f * (headingBarWidth / (float)headingMaterial.mainTexture.width);

                    headingMaterial.mainTexture.wrapMode = TextureWrapMode.Repeat;

                    headingMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayHeading" + hudCamera.GetInstanceID(), new Vector2(headingBarPosition.z * 0.5f, headingBarPosition.w * 0.5f), new Rect(0.0f, 0.0f, 1.0f, 1.0f), drawingLayer);
                    headingMesh.transform.position = new Vector3(headingBarPosition.x + 0.5f * (headingBarPosition.z - screenWidth), 0.5f * (screenHeight - headingBarPosition.w) - headingBarPosition.y, 1.4f);
                    headingMesh.renderer.material = headingMaterial;
                    headingMesh.transform.parent = cameraBody.transform;

                    JUtil.ShowHide(false, headingMesh);

                    if (progradeColorValue.a > 0.0f && showHeadingBarPrograde)
                    {
                        Material progradeIconMaterial = new Material(displayShader);
                        progradeIconMaterial.color = Color.white;
                        Rect texCoord;
                        if (string.IsNullOrEmpty(headingBarProgradeTexture))
                        {
                            progradeIconMaterial.mainTexture = JUtil.GetGizmoTexture();
                            texCoord = GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE);
                        }
                        else
                        {
                            Texture2D progradeTexture = GameDatabase.Instance.GetTexture(headingBarProgradeTexture.EnforceSlashes(), false);
                            if (progradeTexture == null)
                            {
                                JUtil.LogErrorMessage(this, "Failed to find heading bar prograde texture \"{0}\".", headingBarProgradeTexture);
                            }
                            progradeIconMaterial.mainTexture = progradeTexture;
                            texCoord = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
                        }
                        progradeIconMaterial.SetVector("_Color", progradeColorValue);

                        progradeHeadingIconOrigin = headingBarPosition.x + 0.5f * (headingBarPosition.z - screenWidth);

                        progradeHeadingIcon = JUtil.CreateSimplePlane("JSIHeadsUpDisplayHeadingProgradeIcon" + hudCamera.GetInstanceID(), new Vector2(iconPixelSize * 0.5f, iconPixelSize * 0.5f), texCoord, drawingLayer);
                        progradeHeadingIcon.transform.position = new Vector3(progradeHeadingIconOrigin, 0.5f * (screenHeight - headingBarPosition.w) - headingBarPosition.y, 1.35f);
                        progradeHeadingIcon.renderer.material = progradeIconMaterial;
                        progradeHeadingIcon.transform.parent = headingMesh.transform;
                    }
                }
            }

            if (!string.IsNullOrEmpty(verticalBar))
            {
                ConfigNode[] nodes = GameDatabase.Instance.GetConfigNodes("JSIHUD_VERTICAL_BAR");
                string[] vBars = verticalBar.Split(';');
                for (int i = 0; i < vBars.Length; ++i)
                {
                    for (int j = 0; j < nodes.Length; ++j)
                    {
                        if (nodes[j].HasValue("name") && vBars[i].Trim() == nodes[j].GetValue("name"))
                        {
                            try
                            {
                                VerticalBar vb = new VerticalBar(nodes[j], screenWidth, screenHeight, drawingLayer, displayShader, cameraBody);
                                verticalBars.Add(vb);
                            }
                            catch (Exception e)
                            {
                                JUtil.LogErrorMessage(this, "Error parsing JSIHUD_VERTICAL_BAR: {0}", e);
                            }
                            break;
                        }
                    }
                }
            }
        }
Esempio n. 4
0
        public MonitorPage(int idNum, ConfigNode node, RasterPropMonitor thatMonitor)
        {
            ourMonitor   = thatMonitor;
            screenWidth  = ourMonitor.screenWidth;
            screenHeight = ourMonitor.screenHeight;
            cameraAspect = ourMonitor.cameraAspect;
            cameraObject = thatMonitor.cameraStructure;
            defaultColor = ourMonitor.defaultFontTintValue;
            screenXMin   = 0;
            screenYMin   = 0;

            pageNumber = idNum;
            isMutable  = false;
            if (!node.HasData)
            {
                throw new ArgumentException("Empty page?");
            }

            if (node.HasValue("name"))
            {
                string value = node.GetValue("name").Trim();
                if (!IsValidPageName(value))
                {
                    JUtil.LogMessage(ourMonitor, "Warning, name given for page #{0} is invalid, ignoring.", pageNumber);
                }
                else
                {
                    name = value;
                }
            }
            else
            {
                JUtil.LogMessage(ourMonitor, "Warning, page #{0} has no name. It's much better if it does.", pageNumber);
            }

            isDefault |= node.HasValue("default");

            if (node.HasValue("button"))
            {
                SmarterButton.CreateButton(thatMonitor.internalProp, node.GetValue("button"), this, thatMonitor.PageButtonClick);
            }

            // Page locking system -- simple locking:
            simpleLockingPage |= node.HasValue("lockingPage");
            // and name-based locking.
            if (node.HasValue("disableSwitchingTo"))
            {
                string[] tokens = node.GetValue("disableSwitchingTo").Split(',');
                foreach (string token in tokens)
                {
                    disableSwitchingTo.Add(token.Trim());
                }
            }

            unlocker |= node.HasValue("unlockerPage");

            if (node.HasValue("localMargins"))
            {
                Vector4 margindata = ConfigNode.ParseVector4(node.GetValue("localMargins"));
                screenXMin   = (int)margindata.x;
                screenYMin   = (int)margindata.y;
                screenWidth  = screenWidth - (int)margindata.z - screenXMin;
                screenHeight = screenHeight - (int)margindata.w - screenYMin;
            }

            pageFont = node.GetInt("defaultFontNumber") ?? 0;

            if (node.HasValue("defaultFontTint"))
            {
                defaultColor = ConfigNode.ParseColor32(node.GetValue("defaultFontTint"));
            }

            if (node.HasValue("techsRequired"))
            {
                techsRequired = node.GetValue("techsRequired").Split(new[] { ' ', ',', ';', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            }

            if (node.HasValue("fallbackOnNoTech"))
            {
                fallbackPageName = node.GetValue("fallbackOnNoTech").Trim();
            }

            if (node.HasNode("CONTEXTREDIRECT"))
            {
                ConfigNode[] redirectnodes = node.GetNodes("CONTEXTREDIRECT");
                for (int i = 0; i < redirectnodes.Length; ++i)
                {
                    string[] redirects = redirectnodes[i].GetValues("redirect");
                    for (int j = 0; j < redirects.Length; ++j)
                    {
                        string[] tokens = redirects[j].Split(',');
                        if (tokens.Length > 2 || !IsValidPageName(tokens[0].Trim()) || !IsValidPageName(tokens[1].Trim()))
                        {
                            JUtil.LogMessage(ourMonitor, "Warning, invalid page redirect statement on page #{0}.", pageNumber);
                            continue;
                        }
                        redirectPages[tokens[0].Trim()] = tokens[1].Trim();
                    }

                    string[] renumbers = redirectnodes[i].GetValues("renumber");
                    for (int j = 0; j < renumbers.Length; ++j)
                    {
                        string[] tokens = renumbers[j].Split(',');
                        if (tokens.Length > 2)
                        {
                            JUtil.LogMessage(ourMonitor, "Warning, invalid global button redirect statement on page #{0}: requires two arguments.", pageNumber);
                            continue;
                        }
                        int from, to;
                        if (!int.TryParse(tokens[0], out from) || !int.TryParse(tokens[1], out to))
                        {
                            JUtil.LogMessage(ourMonitor, "Warning, invalid global button redirect statement on page #{0}: something isn't a number", pageNumber);
                            continue;
                        }
                        redirectGlobals[from] = to;
                    }
                }

                JUtil.LogMessage(this, "Page '{2}' (#{0}) registers {1} page redirects and {3} global button redirects.", idNum, redirectPages.Count, name, redirectGlobals.Count);
            }

            foreach (ConfigNode handlerNode in node.GetNodes("PAGEHANDLER"))
            {
                MonoBehaviour         handlerModule;
                HandlerSupportMethods supportMethods;
                MethodInfo            handlerMethod = InstantiateHandler(handlerNode, ourMonitor, out handlerModule, out supportMethods);
                if (handlerMethod != null && handlerModule != null)
                {
                    try
                    {
                        pageHandlerMethod = (Func <int, int, string>)Delegate.CreateDelegate(typeof(Func <int, int, string>), handlerModule, handlerMethod);
                    }
                    catch
                    {
                        JUtil.LogErrorMessage(ourMonitor, "Incorrect signature for the page handler method {0}", handlerModule.name);
                        break;
                    }
                    pageHandlerS      = supportMethods;
                    isMutable         = true;
                    pageHandlerModule = handlerModule;
                    break;
                }
            }

            if (pageHandlerMethod == null)
            {
                if (node.HasValue("text"))
                {
                    text       = JUtil.LoadPageDefinition(node.GetValue("text"));
                    isMutable |= text.IndexOf("$&$", StringComparison.Ordinal) != -1;
                }
            }

            if (node.HasValue("textOverlay"))
            {
                textOverlay = JUtil.LoadPageDefinition(node.GetValue("textOverlay"));
            }

            foreach (ConfigNode handlerNode in node.GetNodes("BACKGROUNDHANDLER"))
            {
                MonoBehaviour         handlerModule;
                HandlerSupportMethods supportMethods;
                MethodInfo            handlerMethod = InstantiateHandler(handlerNode, ourMonitor, out handlerModule, out supportMethods);
                if (handlerMethod != null && handlerModule != null)
                {
                    try
                    {
                        backgroundHandlerMethod = (Func <RenderTexture, float, bool>)Delegate.CreateDelegate(typeof(Func <RenderTexture, float, bool>), handlerModule, handlerMethod);
                    }
                    catch
                    {
                        JUtil.LogErrorMessage(ourMonitor, "Incorrect signature for the background handler method {0}", handlerModule.name);
                        break;
                    }
                    backgroundHandlerS      = supportMethods;
                    isMutable               = true;
                    showNoSignal            = node.HasValue("showNoSignal");
                    background              = BackgroundType.Handler;
                    backgroundHandlerModule = handlerModule;
                    break;
                }
            }

            if (background == BackgroundType.None)
            {
                if (node.HasValue("cameraTransform"))
                {
                    isMutable  = true;
                    background = BackgroundType.Camera;
                    camera     = node.GetValue("cameraTransform");
                    cameraFOV  = defaultFOV;

                    cameraFlickerChance = node.GetFloat("flickerChance") ?? 0;
                    cameraFlickerRange  = node.GetInt("flickerRange") ?? 0;

                    if (node.HasValue("fov"))
                    {
                        float fov;
                        cameraFOV = float.TryParse(node.GetValue("fov"), out fov) ? fov : defaultFOV;
                    }
                    else if (node.HasValue("zoomFov") && node.HasValue("zoomButtons"))
                    {
                        Vector3 zoomFov     = ConfigNode.ParseVector3(node.GetValue("zoomFov"));
                        Vector2 zoomButtons = ConfigNode.ParseVector2(node.GetValue("zoomButtons"));
                        if ((int)zoomFov.z != 0 && ((int)zoomButtons.x != (int)zoomButtons.y))
                        {
                            maxFOV         = Math.Max(zoomFov.x, zoomFov.y);
                            minFOV         = Math.Min(zoomFov.x, zoomFov.y);
                            zoomSteps      = (int)zoomFov.z;
                            zoomUpButton   = (int)zoomButtons.x;
                            zoomDownButton = (int)zoomButtons.y;
                            zoomSkip       = (maxFOV - minFOV) / zoomSteps;
                            currentZoom    = 0;
                            cameraFOV      = maxFOV;
                        }
                        else
                        {
                            JUtil.LogMessage(ourMonitor, "Ignored invalid camera zoom settings on page {0}.", pageNumber);
                        }
                    }
                }
            }
            if (background == BackgroundType.None)
            {
                if (node.HasValue("textureURL"))
                {
                    string textureURL = node.GetValue("textureURL").EnforceSlashes();
                    if (GameDatabase.Instance.ExistsTexture(textureURL))
                    {
                        backgroundTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                        background        = BackgroundType.Texture;
                    }
                }
            }
            if (node.HasValue("textureInterlayURL"))
            {
                string textureURL = node.GetValue("textureInterlayURL").EnforceSlashes();
                if (GameDatabase.Instance.ExistsTexture(textureURL))
                {
                    interlayTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                }
                else
                {
                    JUtil.LogErrorMessage(ourMonitor, "Interlay texture {0} could not be loaded.", textureURL);
                }
            }
            if (node.HasValue("textureOverlayURL"))
            {
                string textureURL = node.GetValue("textureOverlayURL").EnforceSlashes();
                if (GameDatabase.Instance.ExistsTexture(textureURL))
                {
                    overlayTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                }
                else
                {
                    JUtil.LogErrorMessage(ourMonitor, "Overlay texture {0} could not be loaded.", textureURL);
                }
            }
        }
Esempio n. 5
0
        public MonitorPage(int idNum, ConfigNode node, RasterPropMonitor thatMonitor)
        {
            ourMonitor = thatMonitor;
            screenWidth = ourMonitor.screenWidth;
            screenHeight = ourMonitor.screenHeight;
            cameraAspect = ourMonitor.cameraAspect;
            cameraObject = thatMonitor.cameraStructure;
            defaultColor = ourMonitor.defaultFontTintValue;
            screenXMin = 0;
            screenYMin = 0;

            pageNumber = idNum;
            isMutable = false;
            if (!node.HasData)
            {
                throw new ArgumentException("Empty page?");
            }

            if (node.HasValue("name"))
            {
                string value = node.GetValue("name").Trim();
                if (!IsValidPageName(value))
                {
                    JUtil.LogMessage(ourMonitor, "Warning, name given for page #{0} is invalid, ignoring.", pageNumber);
                }
                else
                {
                    name = value;
                }
            }
            else
            {
                JUtil.LogMessage(ourMonitor, "Warning, page #{0} has no name. It's much better if it does.", pageNumber);
            }

            isDefault |= node.HasValue("default");

            if (node.HasValue("button"))
            {
                SmarterButton.CreateButton(thatMonitor.internalProp, node.GetValue("button"), this, thatMonitor.PageButtonClick);
            }

            // Page locking system -- simple locking:
            simpleLockingPage |= node.HasValue("lockingPage");
            // and name-based locking.
            if (node.HasValue("disableSwitchingTo"))
            {
                string[] tokens = node.GetValue("disableSwitchingTo").Split(',');
                foreach (string token in tokens)
                {
                    disableSwitchingTo.Add(token.Trim());
                }
            }

            unlocker |= node.HasValue("unlockerPage");

            if (node.HasValue("localMargins"))
            {
                Vector4 margindata = ConfigNode.ParseVector4(node.GetValue("localMargins"));
                screenXMin = (int)margindata.x;
                screenYMin = (int)margindata.y;
                screenWidth = screenWidth - (int)margindata.z - screenXMin;
                screenHeight = screenHeight - (int)margindata.w - screenYMin;
            }

            pageFont = node.GetInt("defaultFontNumber") ?? 0;

            if (node.HasValue("defaultFontTint"))
            {
                defaultColor = ConfigNode.ParseColor32(node.GetValue("defaultFontTint"));
            }

            if (node.HasValue("techsRequired"))
            {
                techsRequired = node.GetValue("techsRequired").Split(new[] { ' ', ',', ';', '\t', '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries).ToList();
            }

            if (node.HasValue("fallbackOnNoTech"))
            {
                fallbackPageName = node.GetValue("fallbackOnNoTech").Trim();
            }

            if (node.HasNode("CONTEXTREDIRECT"))
            {
                foreach (string content in node.GetNode("CONTEXTREDIRECT").GetValues("redirect"))
                {
                    string[] tokens = content.Split(',');
                    if (tokens.Length > 2 || !IsValidPageName(tokens[0].Trim()) || !IsValidPageName(tokens[1].Trim()))
                    {
                        JUtil.LogMessage(ourMonitor, "Warning, invalid page redirect statement on page #{0}.", pageNumber);
                        continue;
                    }
                    redirectPages[tokens[0].Trim()] = tokens[1].Trim();
                }
                const string valueError = "Warning, invalid global button redirect statement on page #{0}: {1}";
                foreach (string content in node.GetNode("CONTEXTREDIRECT").GetValues("renumber"))
                {
                    string[] tokens = content.Split(',');
                    if (tokens.Length > 2)
                    {
                        JUtil.LogMessage(ourMonitor, valueError, pageNumber, "requires two arguments.");
                        continue;
                    }
                    int from, to;
                    if (!int.TryParse(tokens[0], out from) || !int.TryParse(tokens[1], out to))
                    {
                        JUtil.LogMessage(ourMonitor, valueError, pageNumber, "something isn't a number.");
                        continue;
                    }
                    redirectGlobals[from] = to;
                }
                JUtil.LogMessage(this, "Page '{2}' (#{0}) registers {1} page redirects and {3} global button redirects.", idNum, redirectPages.Count, name, redirectGlobals.Count);
            }

            foreach (ConfigNode handlerNode in node.GetNodes("PAGEHANDLER"))
            {
                MonoBehaviour handlerModule;
                HandlerSupportMethods supportMethods;
                MethodInfo handlerMethod = InstantiateHandler(handlerNode, ourMonitor, out handlerModule, out supportMethods);
                if (handlerMethod != null && handlerModule != null)
                {
                    try
                    {
                        pageHandlerMethod = (Func<int, int, string>)Delegate.CreateDelegate(typeof(Func<int, int, string>), handlerModule, handlerMethod);
                    }
                    catch
                    {
                        JUtil.LogErrorMessage(ourMonitor, "Incorrect signature for the page handler method {0}", handlerModule.name);
                        break;
                    }
                    pageHandlerS = supportMethods;
                    isMutable = true;
                    pageHandlerModule = handlerModule;
                    break;
                }
            }

            if (pageHandlerMethod == null)
            {
                if (node.HasValue("text"))
                {
                    text = JUtil.LoadPageDefinition(node.GetValue("text"));
                    isMutable |= text.IndexOf("$&$", StringComparison.Ordinal) != -1;
                }
            }

            if (node.HasValue("textOverlay"))
            {
                textOverlayBuffer = JUtil.LoadPageDefinition(node.GetValue("textOverlay")).Split(JUtil.LineSeparator, StringSplitOptions.None);
            }

            foreach (ConfigNode handlerNode in node.GetNodes("BACKGROUNDHANDLER"))
            {
                MonoBehaviour handlerModule;
                HandlerSupportMethods supportMethods;
                MethodInfo handlerMethod = InstantiateHandler(handlerNode, ourMonitor, out handlerModule, out supportMethods);
                if (handlerMethod != null && handlerModule != null)
                {
                    try
                    {
                        backgroundHandlerMethod = (Func<RenderTexture, float, bool>)Delegate.CreateDelegate(typeof(Func<RenderTexture, float, bool>), handlerModule, handlerMethod);
                    }
                    catch
                    {
                        JUtil.LogErrorMessage(ourMonitor, "Incorrect signature for the background handler method {0}", handlerModule.name);
                        break;
                    }
                    backgroundHandlerS = supportMethods;
                    isMutable = true;
                    showNoSignal = node.HasValue("showNoSignal");
                    background = BackgroundType.Handler;
                    backgroundHandlerModule = handlerModule;
                    break;
                }
            }

            if (background == BackgroundType.None)
            {
                if (node.HasValue("cameraTransform"))
                {
                    isMutable = true;
                    background = BackgroundType.Camera;
                    camera = node.GetValue("cameraTransform");
                    cameraFOV = defaultFOV;

                    cameraFlickerChance = node.GetFloat("flickerChance") ?? 0;
                    cameraFlickerRange = node.GetInt("flickerRange") ?? 0;

                    if (node.HasValue("fov"))
                    {
                        float fov;
                        cameraFOV = float.TryParse(node.GetValue("fov"), out fov) ? fov : defaultFOV;
                    }
                    else if (node.HasValue("zoomFov") && node.HasValue("zoomButtons"))
                    {
                        Vector3 zoomFov = ConfigNode.ParseVector3(node.GetValue("zoomFov"));
                        Vector2 zoomButtons = ConfigNode.ParseVector2(node.GetValue("zoomButtons"));
                        if ((int)zoomFov.z != 0 && ((int)zoomButtons.x != (int)zoomButtons.y))
                        {
                            maxFOV = Math.Max(zoomFov.x, zoomFov.y);
                            minFOV = Math.Min(zoomFov.x, zoomFov.y);
                            zoomSteps = (int)zoomFov.z;
                            zoomUpButton = (int)zoomButtons.x;
                            zoomDownButton = (int)zoomButtons.y;
                            zoomSkip = (maxFOV - minFOV) / zoomSteps;
                            currentZoom = 0;
                            cameraFOV = maxFOV;
                        }
                        else
                        {
                            JUtil.LogMessage(ourMonitor, "Ignored invalid camera zoom settings on page {0}.", pageNumber);
                        }
                    }
                }
            }
            if (background == BackgroundType.None)
            {
                if (node.HasValue("textureURL"))
                {
                    string textureURL = node.GetValue("textureURL").EnforceSlashes();
                    if (GameDatabase.Instance.ExistsTexture(textureURL))
                    {
                        backgroundTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                        background = BackgroundType.Texture;
                    }
                }
            }
            if (node.HasValue("textureInterlayURL"))
            {
                string textureURL = node.GetValue("textureInterlayURL").EnforceSlashes();
                if (GameDatabase.Instance.ExistsTexture(textureURL))
                {
                    interlayTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                }
                else
                {
                    JUtil.LogErrorMessage(ourMonitor, "Interlay texture could not be loaded.");
                }
            }
            if (node.HasValue("textureOverlayURL"))
            {
                string textureURL = node.GetValue("textureOverlayURL").EnforceSlashes();
                if (GameDatabase.Instance.ExistsTexture(textureURL))
                {
                    overlayTexture = GameDatabase.Instance.GetTexture(textureURL, false);
                }
                else
                {
                    JUtil.LogErrorMessage(ourMonitor, "Overlay texture {0} could not be loaded.", textureURL);
                }
            }
        }
		public bool RenderHUD(RenderTexture screen, float cameraAspect)
		{
			if (screen == null || !startupComplete || HighLogic.LoadedSceneIsEditor)
				return false;

			// Clear the background, if configured.
			GL.Clear(true, true, backgroundColorValue);

			// Configure the camera, if configured.
			// MOARdV: Might be worthwhile to refactor the flying camera so
			// it is created in Start (like new FlyingCamera(part, cameraTransform)),
			// and pass the screen, FoV, and aspect ratio (or just screen and
			// FoV) as Render parameters, so there's no need to test if the
			// camera's been created every render call.
			if (cameraObject == null && !string.IsNullOrEmpty(cameraTransform)) {
				cameraObject = new FlyingCamera(part, screen, cameraAspect);
				cameraObject.PointCamera(cameraTransform, hudFov);
			}

			// Draw the camera's view, if configured.
			if (cameraObject != null) {
				cameraObject.Render();
			}

			// Configure the matrix so that the origin is the center of the screen.
			GL.PushMatrix();

			// Draw the HUD ladder
			// MOARdV note, 2014/03/19: swapping the y values, to invert the
			// coordinates so the prograde icon is right-side up.
			GL.LoadPixelMatrix(-horizonSize.x * 0.5f, horizonSize.x * 0.5f, horizonSize.y * 0.5f, -horizonSize.y * 0.5f);
			GL.Viewport(new Rect((screen.width - horizonSize.x) * 0.5f, (screen.height - horizonSize.y) * 0.5f, horizonSize.x, horizonSize.y));

			Vector3 coM = vessel.findWorldCenterOfMass();
			Vector3 up = (coM - vessel.mainBody.position).normalized;
			Vector3 forward = vessel.GetTransform().up;
			Vector3 right = vessel.GetTransform().right;
			Vector3 top = Vector3.Cross(right, forward);
			Vector3 north = Vector3.Exclude(up, (vessel.mainBody.position + (Vector3d)vessel.mainBody.transform.up * vessel.mainBody.Radius) - coM).normalized;

			Vector3d velocityVesselSurface = vessel.orbit.GetVel() - vessel.mainBody.getRFrmVel(coM);
			Vector3 velocityVesselSurfaceUnit = velocityVesselSurface.normalized;

			if (ladderMaterial) {
				// Figure out the texture coordinate scaling for the ladder.
				float ladderTextureOffset = horizonTextureSize.y / ladderMaterial.mainTexture.height;

				float cosUp = Vector3.Dot(forward, up);
				float cosRoll = Vector3.Dot(top, up);
				float sinRoll = Vector3.Dot(right, up);

				var normalizedRoll = new Vector2(cosRoll, sinRoll);
				normalizedRoll.Normalize();
				if (normalizedRoll.magnitude < 0.99f) {
					// If we're hitting +/- 90 nearly perfectly, the sin and cos will
					// be too far out of whack to normalize.  Arbitrarily pick
					// a roll of 0.0.
					normalizedRoll.x = 1.0f;
					normalizedRoll.y = 0.0f;
				}
				cosRoll = normalizedRoll.x;
				sinRoll = normalizedRoll.y;

				// Mihara: I'm pretty sure this was negative of what it should actually be, at least according to my mockup.
				float pitch = -(Mathf.Asin(cosUp) * Mathf.Rad2Deg);

				float ladderMidpointCoord;
				if (use360horizon) {
					// Straight up is texture coord 0.75;
					// Straight down is TC 0.25;
					ladderMidpointCoord = JUtil.DualLerp(0.25f, 0.75f, -90f, 90f, pitch);
				} else {
					// Straight up is texture coord 1.0;
					// Straight down is TC 0.0;
					ladderMidpointCoord = JUtil.DualLerp(0.0f, 1.0f, -90f, 90f, pitch);
				}

				ladderMaterial.SetPass(0);
				GL.Begin(GL.QUADS);

				// transform -x -y
				GL.TexCoord2(0.5f + horizonTextureSize.x, ladderMidpointCoord - ladderTextureOffset);
				GL.Vertex3(cosRoll * horizonSize.x + sinRoll * horizonSize.y, -sinRoll * horizonSize.x + cosRoll * horizonSize.y, 0.0f);

				// transform +x -y
				GL.TexCoord2(0.5f - horizonTextureSize.x, ladderMidpointCoord - ladderTextureOffset);
				GL.Vertex3(-cosRoll * horizonSize.x + sinRoll * horizonSize.y, sinRoll * horizonSize.x + cosRoll * horizonSize.y, 0.0f);

				// transform +x +y
				GL.TexCoord2(0.5f - horizonTextureSize.x, ladderMidpointCoord + ladderTextureOffset);
				GL.Vertex3(-cosRoll * horizonSize.x - sinRoll * horizonSize.y, sinRoll * horizonSize.x - cosRoll * horizonSize.y, 0.0f);

				// transform -x +y
				GL.TexCoord2(0.5f + horizonTextureSize.x, ladderMidpointCoord + ladderTextureOffset);
				GL.Vertex3(cosRoll * horizonSize.x - sinRoll * horizonSize.y, -sinRoll * horizonSize.x - cosRoll * horizonSize.y, 0.0f);
				GL.End();

				float AoA = velocityVesselSurfaceUnit.AngleInPlane(right, forward);
				float AoATC;
				if (use360horizon) {
					// Straight up is texture coord 0.75;
					// Straight down is TC 0.25;
					AoATC = JUtil.DualLerp(0.25f, 0.75f, -90f, 90f, pitch + AoA);
				} else {
					// Straight up is texture coord 1.0;
					// Straight down is TC 0.0;
					AoATC = JUtil.DualLerp(0.0f, 1.0f, -90f, 90f, pitch + AoA);
				}

				float Ypos = JUtil.DualLerp(
					             -horizonSize.y, horizonSize.y,
					             ladderMidpointCoord - ladderTextureOffset, ladderMidpointCoord + ladderTextureOffset, 
					             AoATC);

				// Placing the icon on the (0, Ypos) location, so simplify the transform.
				DrawIcon(-sinRoll * Ypos, -cosRoll * Ypos, GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE), progradeColorValue);
			}

			// Draw the rest of the HUD stuff (0,0) is the top left corner of the screen.
			GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
			GL.Viewport(new Rect(0, 0, screen.width, screen.height));

			if (headingMaterial != null) {
				Quaternion rotationSurface = Quaternion.LookRotation(north, up);
				Quaternion rotationVesselSurface = Quaternion.Inverse(Quaternion.Euler(90, 0, 0) * Quaternion.Inverse(vessel.GetTransform().rotation) * rotationSurface);
				float headingTexture = JUtil.DualLerp(0f, 1f, 0f, 360f, rotationVesselSurface.eulerAngles.y);
				float headingTextureOffset = (headingBarWidth / headingMaterial.mainTexture.width) / 2;

				headingMaterial.SetPass(0);
				GL.Begin(GL.QUADS);
				GL.TexCoord2(headingTexture - headingTextureOffset, 1.0f);
				GL.Vertex3(headingBarPosition.x, headingBarPosition.y, 0.0f);
				GL.TexCoord2(headingTexture + headingTextureOffset, 1.0f);
				GL.Vertex3(headingBarPosition.x + headingBarPosition.z, headingBarPosition.y, 0.0f);
				GL.TexCoord2(headingTexture + headingTextureOffset, 0.0f);
				GL.Vertex3(headingBarPosition.x + headingBarPosition.z, headingBarPosition.y + headingBarPosition.w, 0.0f);
				GL.TexCoord2(headingTexture - headingTextureOffset, 0.0f);
				GL.Vertex3(headingBarPosition.x, headingBarPosition.y + headingBarPosition.w, 0.0f);
				GL.End();

				if (showHeadingBarPrograde) {
					float slipAngle = velocityVesselSurfaceUnit.AngleInPlane(up, forward);
					float slipTC = JUtil.DualLerp(0f, 1f, 0f, 360f, rotationVesselSurface.eulerAngles.y + slipAngle);
					float slipIconX = JUtil.DualLerp(headingBarPosition.x, headingBarPosition.x + headingBarPosition.z, headingTexture - headingTextureOffset, headingTexture + headingTextureOffset, slipTC);
					DrawIcon(slipIconX, headingBarPosition.y + headingBarPosition.w * 0.5f, GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE), progradeColorValue);
				}
			}

			if (vertBar1Material != null) {
				float value = comp.ProcessVariable(vertBar1Variable).MassageToFloat();
				if (float.IsNaN(value)) {
					value = 0.0f;
				}

				if (vertBar1UseLog10) {
					value = JUtil.PseudoLog10(value);
				}

				float vertBar1TexCoord = JUtil.DualLerp(vertBar1TextureLimit.x, vertBar1TextureLimit.y, vertBar1Limit.x, vertBar1Limit.y, value);

				vertBar1Material.SetPass(0);
				GL.Begin(GL.QUADS);
				GL.TexCoord2(0.0f, vertBar1TexCoord + vertBar1TextureSize);
				GL.Vertex3(vertBar1Position.x, vertBar1Position.y, 0.0f);
				GL.TexCoord2(1.0f, vertBar1TexCoord + vertBar1TextureSize);
				GL.Vertex3(vertBar1Position.x + vertBar1Position.z, vertBar1Position.y, 0.0f);
				GL.TexCoord2(1.0f, vertBar1TexCoord - vertBar1TextureSize);
				GL.Vertex3(vertBar1Position.x + vertBar1Position.z, vertBar1Position.y + vertBar1Position.w, 0.0f);
				GL.TexCoord2(0.0f, vertBar1TexCoord - vertBar1TextureSize);
				GL.Vertex3(vertBar1Position.x, vertBar1Position.y + vertBar1Position.w, 0.0f);
				GL.End();
			}

			if (vertBar2Material != null) {
				float value = comp.ProcessVariable(vertBar2Variable).MassageToFloat();
				if (float.IsNaN(value)) {
					value = 0.0f;
				}

				if (vertBar2UseLog10) {
					value = JUtil.PseudoLog10(value);
				}

				float vertBar2TexCoord = JUtil.DualLerp(vertBar2TextureLimit.x, vertBar2TextureLimit.y, vertBar2Limit.x, vertBar2Limit.y, value);

				vertBar2Material.SetPass(0);
				GL.Begin(GL.QUADS);
				GL.TexCoord2(0.0f, vertBar2TexCoord + vertBar2TextureSize);
				GL.Vertex3(vertBar2Position.x, vertBar2Position.y, 0.0f);
				GL.TexCoord2(1.0f, vertBar2TexCoord + vertBar2TextureSize);
				GL.Vertex3(vertBar2Position.x + vertBar2Position.z, vertBar2Position.y, 0.0f);
				GL.TexCoord2(1.0f, vertBar2TexCoord - vertBar2TextureSize);
				GL.Vertex3(vertBar2Position.x + vertBar2Position.z, vertBar2Position.y + vertBar2Position.w, 0.0f);
				GL.TexCoord2(0.0f, vertBar2TexCoord - vertBar2TextureSize);
				GL.Vertex3(vertBar2Position.x, vertBar2Position.y + vertBar2Position.w, 0.0f);
				GL.End();
			}

			if (overlayMaterial != null) {
				overlayMaterial.SetPass(0);
				GL.Begin(GL.QUADS);
				GL.TexCoord2(0.0f, 1.0f);
				GL.Vertex3(0.0f, 0.0f, 0.0f);
				GL.TexCoord2(1.0f, 1.0f);
				GL.Vertex3(screen.width, 0.0f, 0.0f);
				GL.TexCoord2(1.0f, 0.0f);
				GL.Vertex3(screen.width, screen.height, 0.0f);
				GL.TexCoord2(0.0f, 0.0f);
				GL.Vertex3(0.0f, screen.height, 0.0f);
				GL.End();
			}

			GL.PopMatrix();

			return true;
		}
		public bool RenderCamera(RenderTexture screen, float cameraAspect)
		{
			// Just in case.
			if (HighLogic.LoadedSceneIsEditor) {
				return false;
			}

			if(cameras.Count < 1) {
				return false;
			}

			var activeCamera = cameras[currentCamera];
			if (string.IsNullOrEmpty(activeCamera.cameraTransform)) {
				return false;
			}

			if (cameraObject == null) {
				cameraObject = new FlyingCamera(part, screen, cameraAspect);
				cameraObject.PointCamera(activeCamera.cameraTransform, activeCamera.currentFoV);
			}

			cameraObject.FOV = activeCamera.currentFoV;

			// Negate pitch - the camera object treats a negative pitch as "up"
			if (cameraObject.Render(activeCamera.currentYaw, -activeCamera.currentPitch)) {
				ITargetable target = FlightGlobals.fetch.VesselTarget;

				bool drawSomething = ((gizmoTexture != null && target != null && showTargetIcon) || homeCrosshairMaterial.color.a > 0);

				if (drawSomething) {
					GL.PushMatrix();
					GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
				}

				if (gizmoTexture != null && target != null && showTargetIcon) {
					// Figure out which direction the target is.
					Vector3 targetDisplacement = target.GetTransform().position - cameraObject.GetTransform().position;
					targetDisplacement.Normalize();

					// Transform it using the active camera's rotation.
					var targetDisp = GetNormalizedScreenPosition(activeCamera, targetDisplacement, cameraAspect);

					var iconCenter = new Vector2(screen.width * targetDisp.x, screen.height * targetDisp.y);

					// Apply some clamping values to force the icon to stay on screen
					iconCenter.x = Math.Max(iconPixelSize * 0.5f, iconCenter.x);
					iconCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, iconCenter.x);
					iconCenter.y = Math.Max(iconPixelSize * 0.5f, iconCenter.y);
					iconCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, iconCenter.y);

					var position = new Rect(iconCenter.x - iconPixelSize * 0.5f, iconCenter.y - iconPixelSize * 0.5f, iconPixelSize, iconPixelSize);

					Graphics.DrawTexture(position, gizmoTexture, GizmoIcons.GetIconLocation(GizmoIcons.IconType.TARGETPLUS), 0, 0, 0, 0, iconMaterial);
				}

				if (homeCrosshairMaterial.color.a > 0) {
					// Mihara: Reference point cameras are different enough to warrant it.
					var cameraForward = cameraObject.GetTransformForward();
					var crossHairCenter = GetNormalizedScreenPosition(activeCamera, cameraForward, cameraAspect);
					crossHairCenter.x *= screen.width;
					crossHairCenter.y *= screen.height;
					crossHairCenter.x = Math.Max(iconPixelSize * 0.5f, crossHairCenter.x);
					crossHairCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, crossHairCenter.x);
					crossHairCenter.y = Math.Max(iconPixelSize * 0.5f, crossHairCenter.y);
					crossHairCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, crossHairCenter.y);

					float zoomAdjustedIconSize = iconPixelSize * Mathf.Tan(Mathf.Deg2Rad * activeCamera.fovLimits.y * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * activeCamera.currentFoV * 0.5f); 

					homeCrosshairMaterial.SetPass(0);
					GL.Begin(GL.LINES);
					GL.Vertex3(crossHairCenter.x - zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
					GL.Vertex3(crossHairCenter.x + zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
					GL.Vertex3(crossHairCenter.x, crossHairCenter.y - zoomAdjustedIconSize * 0.5f, 0.0f);
					GL.Vertex3(crossHairCenter.x, crossHairCenter.y + zoomAdjustedIconSize * 0.5f, 0.0f);
					GL.End();
				}

				if (drawSomething) {
					GL.PopMatrix();
				}

				return true;
			}
			return false;
		}
Esempio n. 8
0
        public bool RenderCamera(RenderTexture screen, float cameraAspect)
        {
            // Just in case.
            if (HighLogic.LoadedSceneIsEditor)
            {
                return(false);
            }

            if (cameras.Count < 1)
            {
                return(false);
            }

            var activeCamera = cameras[currentCamera];

            if (string.IsNullOrEmpty(activeCamera.cameraTransform))
            {
                return(false);
            }

            if (cameraObject == null)
            {
                cameraObject = new FlyingCamera(part, cameraAspect);
                cameraObject.PointCamera(activeCamera.cameraTransform, activeCamera.currentFoV);
            }

            cameraObject.FOV = activeCamera.currentFoV;

            if (rentexWidth == 0)
            {
                rentexWidth  = screen.width;
                rentexHeight = screen.height;

                // Note to self: when rentex dims != screen dims, the FOV seems to be wrong (like FOV is smaller).
            }
            if (renderTex == null)
            {
                renderTex = new RenderTexture(rentexWidth, rentexHeight, screen.depth);
                renderTex.Create();
            }

            // Negate pitch - the camera object treats a negative pitch as "up"
            if (cameraObject.Render(renderTex, activeCamera.currentYaw, -activeCamera.currentPitch))
            {
                if (cameraEffectMaterial != null)
                {
                    cameraEffectMaterial.SetVector("_ImageDims", new Vector4((float)renderTex.width, (float)renderTex.height, 1.0f / (float)renderTex.width, 1.0f / (float)renderTex.height));

                    for (int i = 0; i < ceVariables.Count; ++i)
                    {
                        float value = ceVariables[i].value.AsFloat();
                        cameraEffectMaterial.SetFloat(ceVariables[i].variable, value);
                    }

                    Graphics.Blit(renderTex, screen, cameraEffectMaterial);
                }
                else
                {
                    Graphics.Blit(renderTex, screen);
                }
                renderTex.DiscardContents();

                ITargetable target = FlightGlobals.fetch.VesselTarget;

                bool drawSomething = ((gizmoTexture != null && target != null && showTargetIcon) || homeCrosshairMaterial.color.a > 0);

                if (drawSomething)
                {
                    GL.PushMatrix();
                    GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
                }

                if (gizmoTexture != null && target != null && showTargetIcon)
                {
                    // Figure out which direction the target is.
                    Vector3 targetDisplacement = target.GetTransform().position - cameraObject.GetTransform().position;
                    targetDisplacement.Normalize();

                    // Transform it using the active camera's rotation.
                    var targetDisp = GetNormalizedScreenPosition(activeCamera, targetDisplacement, cameraAspect);

                    var iconCenter = new Vector2(screen.width * targetDisp.x, screen.height * targetDisp.y);

                    // Apply some clamping values to force the icon to stay on screen
                    iconCenter.x = Math.Max(iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.y = Math.Max(iconPixelSize * 0.5f, iconCenter.y);
                    iconCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, iconCenter.y);

                    var position = new Rect(iconCenter.x - iconPixelSize * 0.5f, iconCenter.y - iconPixelSize * 0.5f, iconPixelSize, iconPixelSize);

                    Graphics.DrawTexture(position, gizmoTexture, GizmoIcons.GetIconLocation(GizmoIcons.IconType.TARGETPLUS), 0, 0, 0, 0, iconMaterial);
                }

                if (homeCrosshairMaterial.color.a > 0)
                {
                    // Mihara: Reference point cameras are different enough to warrant it.
                    var cameraForward   = cameraObject.GetTransformForward();
                    var crossHairCenter = GetNormalizedScreenPosition(activeCamera, cameraForward, cameraAspect);
                    crossHairCenter.x *= screen.width;
                    crossHairCenter.y *= screen.height;
                    crossHairCenter.x  = Math.Max(iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.x  = Math.Min(screen.width - iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.y  = Math.Max(iconPixelSize * 0.5f, crossHairCenter.y);
                    crossHairCenter.y  = Math.Min(screen.height - iconPixelSize * 0.5f, crossHairCenter.y);

                    float zoomAdjustedIconSize = iconPixelSize * Mathf.Tan(Mathf.Deg2Rad * activeCamera.fovLimits.y * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * activeCamera.currentFoV * 0.5f);

                    homeCrosshairMaterial.SetPass(0);
                    GL.Begin(GL.LINES);
                    GL.Vertex3(crossHairCenter.x - zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x + zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y - zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y + zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.End();
                }

                if (drawSomething)
                {
                    GL.PopMatrix();
                }

                return(true);
            }
            else if (skipMissingCameras)
            {
                // This will handle cameras getting ejected while in use.
                SelectNextCamera();
            }

            return(false);
        }
Esempio n. 9
0
        /// <summary>
        /// Initialize the renderable game objects for the HUD.
        /// </summary>
        /// <param name="screenWidth"></param>
        /// <param name="screenHeight"></param>
        void InitializeRenderables(RenderTexture screen)
        {
            float screenWidth  = (float)screen.width;
            float screenHeight = (float)screen.height;

            Shader displayShader = JUtil.LoadInternalShader("RPM-DisplayShader");

            if (!string.IsNullOrEmpty(cameraTransform))
            {
                cameraObject = new FlyingCamera(part, screen, hudCamera.aspect);
                cameraObject.PointCamera(cameraTransform, hudFov);
            }

            if (!string.IsNullOrEmpty(staticOverlay))
            {
                Material overlayMaterial = new Material(displayShader);
                overlayMaterial.color = Color.white;
                Texture overlayTexture = GameDatabase.Instance.GetTexture(staticOverlay.EnforceSlashes(), false);
                overlayMaterial.mainTexture = overlayTexture;

                overlayMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayOverlay" + hudCamera.GetInstanceID(), screenWidth * 0.5f, drawingLayer);
                overlayMesh.transform.position = new Vector3(0, 0, 1.0f);
                overlayMesh.GetComponent <Renderer>().material = overlayMaterial;
                overlayMesh.transform.parent = cameraBody.transform;

                JUtil.ShowHide(false, overlayMesh);
            }

            if (!string.IsNullOrEmpty(horizonTexture))
            {
                Shader   ladderShader   = JUtil.LoadInternalShader("RPM-CroppedDisplayShader");
                Material ladderMaterial = new Material(ladderShader);

                // _CropBound is in device normalized coordinates (-1 - +1)
                Vector4 cropBound = new Vector4(-horizonSize.x / screenWidth, -horizonSize.y / screenHeight, horizonSize.x / screenWidth, horizonSize.y / screenHeight);
                ladderMaterial.SetVector("_CropBound", cropBound);
                ladderMaterial.color       = Color.white;
                ladderMaterial.mainTexture = GameDatabase.Instance.GetTexture(horizonTexture.EnforceSlashes(), false);
                if (ladderMaterial.mainTexture != null)
                {
                    float   diagonal        = horizonSize.magnitude / Mathf.Min(horizonSize.x, horizonSize.y) * 0.5f;
                    Vector2 horizonDrawSize = diagonal * horizonSize;
                    horizonTextureSize.x = 0.5f * (horizonTextureSize.x / ladderMaterial.mainTexture.width);
                    horizonTextureSize.y = 0.5f * (horizonTextureSize.y / ladderMaterial.mainTexture.height);

                    ladderMaterial.mainTexture.wrapMode = TextureWrapMode.Clamp;

                    ladderMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayLadder" + hudCamera.GetInstanceID(), horizonDrawSize, new Rect(0.0f, 0.0f, 1.0f, 1.0f), drawingLayer);
                    ladderMesh.transform.position = new Vector3(0, 0, 1.45f);
                    ladderMesh.GetComponent <Renderer>().material = ladderMaterial;
                    ladderMesh.transform.parent = cameraBody.transform;

                    JUtil.ShowHide(false, ladderMesh);

                    if (progradeColorValue.a > 0.0f && showLadderPrograde)
                    {
                        Material progradeIconMaterial = new Material(displayShader);
                        progradeIconMaterial.color = Color.white;
                        Rect texCoord;
                        if (string.IsNullOrEmpty(ladderProgradeTexture))
                        {
                            progradeIconMaterial.mainTexture = JUtil.GetGizmoTexture();
                            texCoord = GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE);
                        }
                        else
                        {
                            Texture2D progradeTexture = GameDatabase.Instance.GetTexture(ladderProgradeTexture.EnforceSlashes(), false);
                            if (progradeTexture == null)
                            {
                                JUtil.LogErrorMessage(this, "Failed to find ladder prograde texture \"{0}\".", ladderProgradeTexture);
                            }
                            progradeIconMaterial.mainTexture = progradeTexture;
                            texCoord = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
                        }
                        progradeIconMaterial.SetVector("_Color", progradeColorValue);

                        progradeLadderIcon = JUtil.CreateSimplePlane("JSIHeadsUpDisplayLadderProgradeIcon" + hudCamera.GetInstanceID(), new Vector2(iconPixelSize * 0.5f, iconPixelSize * 0.5f), texCoord, drawingLayer);
                        progradeLadderIcon.transform.position = new Vector3(0.0f, 0.0f, 1.41f);
                        progradeLadderIcon.GetComponent <Renderer>().material = progradeIconMaterial;
                        progradeLadderIcon.transform.parent = cameraBody.transform;
                    }
                }
            }

            if (!string.IsNullOrEmpty(headingBar))
            {
                Material headingMaterial = new Material(displayShader);
                headingMaterial.color       = Color.white;
                headingMaterial.mainTexture = GameDatabase.Instance.GetTexture(headingBar.EnforceSlashes(), false);
                if (headingMaterial.mainTexture != null)
                {
                    headingBarTextureWidth = 0.5f * (headingBarWidth / (float)headingMaterial.mainTexture.width);

                    headingMaterial.mainTexture.wrapMode = TextureWrapMode.Repeat;

                    headingMesh = JUtil.CreateSimplePlane("JSIHeadsUpDisplayHeading" + hudCamera.GetInstanceID(), new Vector2(headingBarPosition.z * 0.5f, headingBarPosition.w * 0.5f), new Rect(0.0f, 0.0f, 1.0f, 1.0f), drawingLayer);
                    headingMesh.transform.position = new Vector3(headingBarPosition.x + 0.5f * (headingBarPosition.z - screenWidth), 0.5f * (screenHeight - headingBarPosition.w) - headingBarPosition.y, 1.4f);
                    headingMesh.GetComponent <Renderer>().material = headingMaterial;
                    headingMesh.transform.parent = cameraBody.transform;

                    JUtil.ShowHide(false, headingMesh);

                    if (progradeColorValue.a > 0.0f && showHeadingBarPrograde)
                    {
                        Material progradeIconMaterial = new Material(displayShader);
                        progradeIconMaterial.color = Color.white;
                        Rect texCoord;
                        if (string.IsNullOrEmpty(headingBarProgradeTexture))
                        {
                            progradeIconMaterial.mainTexture = JUtil.GetGizmoTexture();
                            texCoord = GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE);
                        }
                        else
                        {
                            Texture2D progradeTexture = GameDatabase.Instance.GetTexture(headingBarProgradeTexture.EnforceSlashes(), false);
                            if (progradeTexture == null)
                            {
                                JUtil.LogErrorMessage(this, "Failed to find heading bar prograde texture \"{0}\".", headingBarProgradeTexture);
                            }
                            progradeIconMaterial.mainTexture = progradeTexture;
                            texCoord = new Rect(0.0f, 0.0f, 1.0f, 1.0f);
                        }
                        progradeIconMaterial.SetVector("_Color", progradeColorValue);

                        progradeHeadingIconOrigin = headingBarPosition.x + 0.5f * (headingBarPosition.z - screenWidth);

                        progradeHeadingIcon = JUtil.CreateSimplePlane("JSIHeadsUpDisplayHeadingProgradeIcon" + hudCamera.GetInstanceID(), new Vector2(iconPixelSize * 0.5f, iconPixelSize * 0.5f), texCoord, drawingLayer);
                        progradeHeadingIcon.transform.position = new Vector3(progradeHeadingIconOrigin, 0.5f * (screenHeight - headingBarPosition.w) - headingBarPosition.y, 1.35f);
                        progradeHeadingIcon.GetComponent <Renderer>().material = progradeIconMaterial;
                        progradeHeadingIcon.transform.parent = headingMesh.transform;
                    }
                }
            }

            if (!string.IsNullOrEmpty(verticalBar))
            {
                ConfigNode[] nodes = GameDatabase.Instance.GetConfigNodes("JSIHUD_VERTICAL_BAR");
                string[]     vBars = verticalBar.Split(';');
                for (int i = 0; i < vBars.Length; ++i)
                {
                    for (int j = 0; j < nodes.Length; ++j)
                    {
                        if (nodes[j].HasValue("name") && vBars[i].Trim() == nodes[j].GetValue("name"))
                        {
                            try
                            {
                                VerticalBar vb = new VerticalBar(nodes[j], screenWidth, screenHeight, drawingLayer, displayShader, cameraBody);
                                verticalBars.Add(vb);
                            }
                            catch (Exception e)
                            {
                                JUtil.LogErrorMessage(this, "Error parsing JSIHUD_VERTICAL_BAR: {0}", e);
                            }
                            break;
                        }
                    }
                }
            }

            if (!string.IsNullOrEmpty(horizontalBar))
            {
                ConfigNode[] nodes = GameDatabase.Instance.GetConfigNodes("JSIHUD_HORIZONTAL_BAR");
                string[]     hBars = horizontalBar.Split(';');
                for (int i = 0; i < hBars.Length; ++i)
                {
                    for (int j = 0; j < nodes.Length; ++j)
                    {
                        if (nodes[j].HasValue("name") && hBars[i].Trim() == nodes[j].GetValue("name"))
                        {
                            try
                            {
                                HorizontalBar hb = new HorizontalBar(nodes[j], screenWidth, screenHeight, drawingLayer, displayShader, cameraBody);
                                horizontalBars.Add(hb);
                            }
                            catch (Exception e)
                            {
                                JUtil.LogErrorMessage(this, "Error parsing JSIHUD_HORIZONTAL_BAR: {0}", e);
                            }
                            break;
                        }
                    }
                }
            }
        }
Esempio n. 10
0
        public bool RenderHUD(RenderTexture screen, float cameraAspect)
        {
            if (screen == null || !startupComplete || HighLogic.LoadedSceneIsEditor)
            {
                return(false);
            }

            // Clear the background, if configured.
            GL.Clear(true, true, backgroundColorValue);

            // Configure the camera, if configured.
            // MOARdV: Might be worthwhile to refactor the flying camera so
            // it is created in Start (like new FlyingCamera(part, cameraTransform)),
            // and pass the screen, FoV, and aspect ratio (or just screen and
            // FoV) as Render parameters, so there's no need to test if the
            // camera's been created every render call.
            if (cameraObject == null && !string.IsNullOrEmpty(cameraTransform))
            {
                cameraObject = new FlyingCamera(part, screen, cameraAspect);
                cameraObject.PointCamera(cameraTransform, hudFov);
            }

            // Draw the camera's view, if configured.
            if (cameraObject != null)
            {
                cameraObject.Render();
            }

            // Configure the matrix so that the origin is the center of the screen.
            GL.PushMatrix();

            // Draw the HUD ladder
            // MOARdV note, 2014/03/19: swapping the y values, to invert the
            // coordinates so the prograde icon is right-side up.
            GL.LoadPixelMatrix(-horizonSize.x * 0.5f, horizonSize.x * 0.5f, horizonSize.y * 0.5f, -horizonSize.y * 0.5f);
            GL.Viewport(new Rect((screen.width - horizonSize.x) * 0.5f, (screen.height - horizonSize.y) * 0.5f, horizonSize.x, horizonSize.y));

            Vector3 coM     = vessel.findWorldCenterOfMass();
            Vector3 up      = (coM - vessel.mainBody.position).normalized;
            Vector3 forward = vessel.GetTransform().up;
            Vector3 right   = vessel.GetTransform().right;
            Vector3 top     = Vector3.Cross(right, forward);
            Vector3 north   = Vector3.Exclude(up, (vessel.mainBody.position + (Vector3d)vessel.mainBody.transform.up * vessel.mainBody.Radius) - coM).normalized;

            Vector3d velocityVesselSurface     = vessel.orbit.GetVel() - vessel.mainBody.getRFrmVel(coM);
            Vector3  velocityVesselSurfaceUnit = velocityVesselSurface.normalized;

            if (ladderMaterial)
            {
                // Figure out the texture coordinate scaling for the ladder.
                float ladderTextureOffset = horizonTextureSize.y / ladderMaterial.mainTexture.height;

                float cosUp   = Vector3.Dot(forward, up);
                float cosRoll = Vector3.Dot(top, up);
                float sinRoll = Vector3.Dot(right, up);

                var normalizedRoll = new Vector2(cosRoll, sinRoll);
                normalizedRoll.Normalize();
                if (normalizedRoll.magnitude < 0.99f)
                {
                    // If we're hitting +/- 90 nearly perfectly, the sin and cos will
                    // be too far out of whack to normalize.  Arbitrarily pick
                    // a roll of 0.0.
                    normalizedRoll.x = 1.0f;
                    normalizedRoll.y = 0.0f;
                }
                cosRoll = normalizedRoll.x;
                sinRoll = normalizedRoll.y;

                // Mihara: I'm pretty sure this was negative of what it should actually be, at least according to my mockup.
                float pitch = -(Mathf.Asin(cosUp) * Mathf.Rad2Deg);

                float ladderMidpointCoord;
                if (use360horizon)
                {
                    // Straight up is texture coord 0.75;
                    // Straight down is TC 0.25;
                    ladderMidpointCoord = JUtil.DualLerp(0.25f, 0.75f, -90f, 90f, pitch);
                }
                else
                {
                    // Straight up is texture coord 1.0;
                    // Straight down is TC 0.0;
                    ladderMidpointCoord = JUtil.DualLerp(0.0f, 1.0f, -90f, 90f, pitch);
                }

                ladderMaterial.SetPass(0);
                GL.Begin(GL.QUADS);

                // transform -x -y
                GL.TexCoord2(0.5f + horizonTextureSize.x, ladderMidpointCoord - ladderTextureOffset);
                GL.Vertex3(cosRoll * horizonSize.x + sinRoll * horizonSize.y, -sinRoll * horizonSize.x + cosRoll * horizonSize.y, 0.0f);

                // transform +x -y
                GL.TexCoord2(0.5f - horizonTextureSize.x, ladderMidpointCoord - ladderTextureOffset);
                GL.Vertex3(-cosRoll * horizonSize.x + sinRoll * horizonSize.y, sinRoll * horizonSize.x + cosRoll * horizonSize.y, 0.0f);

                // transform +x +y
                GL.TexCoord2(0.5f - horizonTextureSize.x, ladderMidpointCoord + ladderTextureOffset);
                GL.Vertex3(-cosRoll * horizonSize.x - sinRoll * horizonSize.y, sinRoll * horizonSize.x - cosRoll * horizonSize.y, 0.0f);

                // transform -x +y
                GL.TexCoord2(0.5f + horizonTextureSize.x, ladderMidpointCoord + ladderTextureOffset);
                GL.Vertex3(cosRoll * horizonSize.x - sinRoll * horizonSize.y, -sinRoll * horizonSize.x - cosRoll * horizonSize.y, 0.0f);
                GL.End();

                float AoA = velocityVesselSurfaceUnit.AngleInPlane(right, forward);
                float AoATC;
                if (use360horizon)
                {
                    // Straight up is texture coord 0.75;
                    // Straight down is TC 0.25;
                    AoATC = JUtil.DualLerp(0.25f, 0.75f, -90f, 90f, pitch + AoA);
                }
                else
                {
                    // Straight up is texture coord 1.0;
                    // Straight down is TC 0.0;
                    AoATC = JUtil.DualLerp(0.0f, 1.0f, -90f, 90f, pitch + AoA);
                }

                float Ypos = JUtil.DualLerp(
                    -horizonSize.y, horizonSize.y,
                    ladderMidpointCoord - ladderTextureOffset, ladderMidpointCoord + ladderTextureOffset,
                    AoATC);

                // Placing the icon on the (0, Ypos) location, so simplify the transform.
                DrawIcon(-sinRoll * Ypos, -cosRoll * Ypos, GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE), progradeColorValue);
            }

            // Draw the rest of the HUD stuff (0,0) is the top left corner of the screen.
            GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
            GL.Viewport(new Rect(0, 0, screen.width, screen.height));

            if (headingMaterial != null)
            {
                Quaternion rotationSurface       = Quaternion.LookRotation(north, up);
                Quaternion rotationVesselSurface = Quaternion.Inverse(Quaternion.Euler(90, 0, 0) * Quaternion.Inverse(vessel.GetTransform().rotation) * rotationSurface);
                float      headingTexture        = JUtil.DualLerp(0f, 1f, 0f, 360f, rotationVesselSurface.eulerAngles.y);
                float      headingTextureOffset  = (headingBarWidth / headingMaterial.mainTexture.width) / 2;

                headingMaterial.SetPass(0);
                GL.Begin(GL.QUADS);
                GL.TexCoord2(headingTexture - headingTextureOffset, 1.0f);
                GL.Vertex3(headingBarPosition.x, headingBarPosition.y, 0.0f);
                GL.TexCoord2(headingTexture + headingTextureOffset, 1.0f);
                GL.Vertex3(headingBarPosition.x + headingBarPosition.z, headingBarPosition.y, 0.0f);
                GL.TexCoord2(headingTexture + headingTextureOffset, 0.0f);
                GL.Vertex3(headingBarPosition.x + headingBarPosition.z, headingBarPosition.y + headingBarPosition.w, 0.0f);
                GL.TexCoord2(headingTexture - headingTextureOffset, 0.0f);
                GL.Vertex3(headingBarPosition.x, headingBarPosition.y + headingBarPosition.w, 0.0f);
                GL.End();

                if (showHeadingBarPrograde)
                {
                    float slipAngle = velocityVesselSurfaceUnit.AngleInPlane(up, forward);
                    float slipTC    = JUtil.DualLerp(0f, 1f, 0f, 360f, rotationVesselSurface.eulerAngles.y + slipAngle);
                    float slipIconX = JUtil.DualLerp(headingBarPosition.x, headingBarPosition.x + headingBarPosition.z, headingTexture - headingTextureOffset, headingTexture + headingTextureOffset, slipTC);
                    DrawIcon(slipIconX, headingBarPosition.y + headingBarPosition.w * 0.5f, GizmoIcons.GetIconLocation(GizmoIcons.IconType.PROGRADE), progradeColorValue);
                }
            }

            if (vertBar1Material != null)
            {
                float value = comp.ProcessVariable(vertBar1Variable).MassageToFloat();
                if (float.IsNaN(value))
                {
                    value = 0.0f;
                }

                if (vertBar1UseLog10)
                {
                    value = JUtil.PseudoLog10(value);
                }

                float vertBar1TexCoord = JUtil.DualLerp(vertBar1TextureLimit.x, vertBar1TextureLimit.y, vertBar1Limit.x, vertBar1Limit.y, value);

                vertBar1Material.SetPass(0);
                GL.Begin(GL.QUADS);
                GL.TexCoord2(0.0f, vertBar1TexCoord + vertBar1TextureSize);
                GL.Vertex3(vertBar1Position.x, vertBar1Position.y, 0.0f);
                GL.TexCoord2(1.0f, vertBar1TexCoord + vertBar1TextureSize);
                GL.Vertex3(vertBar1Position.x + vertBar1Position.z, vertBar1Position.y, 0.0f);
                GL.TexCoord2(1.0f, vertBar1TexCoord - vertBar1TextureSize);
                GL.Vertex3(vertBar1Position.x + vertBar1Position.z, vertBar1Position.y + vertBar1Position.w, 0.0f);
                GL.TexCoord2(0.0f, vertBar1TexCoord - vertBar1TextureSize);
                GL.Vertex3(vertBar1Position.x, vertBar1Position.y + vertBar1Position.w, 0.0f);
                GL.End();
            }

            if (vertBar2Material != null)
            {
                float value = comp.ProcessVariable(vertBar2Variable).MassageToFloat();
                if (float.IsNaN(value))
                {
                    value = 0.0f;
                }

                if (vertBar2UseLog10)
                {
                    value = JUtil.PseudoLog10(value);
                }

                float vertBar2TexCoord = JUtil.DualLerp(vertBar2TextureLimit.x, vertBar2TextureLimit.y, vertBar2Limit.x, vertBar2Limit.y, value);

                vertBar2Material.SetPass(0);
                GL.Begin(GL.QUADS);
                GL.TexCoord2(0.0f, vertBar2TexCoord + vertBar2TextureSize);
                GL.Vertex3(vertBar2Position.x, vertBar2Position.y, 0.0f);
                GL.TexCoord2(1.0f, vertBar2TexCoord + vertBar2TextureSize);
                GL.Vertex3(vertBar2Position.x + vertBar2Position.z, vertBar2Position.y, 0.0f);
                GL.TexCoord2(1.0f, vertBar2TexCoord - vertBar2TextureSize);
                GL.Vertex3(vertBar2Position.x + vertBar2Position.z, vertBar2Position.y + vertBar2Position.w, 0.0f);
                GL.TexCoord2(0.0f, vertBar2TexCoord - vertBar2TextureSize);
                GL.Vertex3(vertBar2Position.x, vertBar2Position.y + vertBar2Position.w, 0.0f);
                GL.End();
            }

            if (overlayMaterial != null)
            {
                overlayMaterial.SetPass(0);
                GL.Begin(GL.QUADS);
                GL.TexCoord2(0.0f, 1.0f);
                GL.Vertex3(0.0f, 0.0f, 0.0f);
                GL.TexCoord2(1.0f, 1.0f);
                GL.Vertex3(screen.width, 0.0f, 0.0f);
                GL.TexCoord2(1.0f, 0.0f);
                GL.Vertex3(screen.width, screen.height, 0.0f);
                GL.TexCoord2(0.0f, 0.0f);
                GL.Vertex3(0.0f, screen.height, 0.0f);
                GL.End();
            }

            GL.PopMatrix();

            return(true);
        }
        public void Start()
        {
            // If we're not in the correct location, there's no point doing anything.
            if (!InstallationPathWarning.Warn())
            {
                return;
            }

            if (HighLogic.LoadedSceneIsEditor)
            {
                return;
            }

            try
            {
                rpmComp = RasterPropMonitorComputer.Instantiate(internalProp, true);
                JUtil.LogMessage(this, "Attaching monitor {2}-{1} to {0}", rpmComp.RPMCid, internalProp.propID, internalProp.internalModel.internalName);

                // Install the calculator module.
                rpmComp.UpdateDataRefreshRate(refreshDataRate);

                // Loading the font...
                List<Texture2D> fontTexture = new List<Texture2D>();
                fontTexture.Add(LoadFont(this, internalProp, fontTransform));

                // Damn KSP's config parser!!!
                if (!string.IsNullOrEmpty(emptyColor))
                {
                    emptyColorValue = ConfigNode.ParseColor32(emptyColor);
                }
                if (!string.IsNullOrEmpty(defaultFontTint))
                {
                    defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint);
                }

                if (!string.IsNullOrEmpty(fontDefinition))
                {
                    JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition);
                    fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0];
                }

                // Now that is done, proceed to setting up the screen.

                screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32);
                screenMat = internalProp.FindModelTransform(screenTransform).GetComponent<Renderer>().material;
                foreach (string layerID in textureLayerID.Split())
                {
                    screenMat.SetTexture(layerID.Trim(), screenTexture);
                }

                if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes()))
                {
                    noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false);
                }

                // Create camera instance...
                cameraStructure = new FlyingCamera(part, cameraAspect);

                // The neat trick. IConfigNode doesn't work. No amount of kicking got it to work.
                // Well, we don't need it. GameDatabase, gimme config nodes for all props!
                foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
                {
                    // Now, we know our own prop name.
                    if (node.GetValue("name") == internalProp.propName)
                    {
                        // So this is the configuration of our prop in memory. Nice place.
                        // We know it contains at least one MODULE node, us.
                        // And we know our moduleID, which is the number in order of being listed in the prop.
                        // Therefore the module by that number is our module's own config node.

                        ConfigNode moduleConfig = node.GetNodes("MODULE")[moduleID];
                        ConfigNode[] pageNodes = moduleConfig.GetNodes("PAGE");

                        // Which we can now parse for page definitions.
                        for (int i = 0; i < pageNodes.Length; i++)
                        {
                            // Mwahahaha.
                            try
                            {
                                var newPage = new MonitorPage(i, pageNodes[i], this);
                                activePage = activePage ?? newPage;
                                if (newPage.isDefault)
                                    activePage = newPage;
                                pages.Add(newPage);
                            }
                            catch (ArgumentException e)
                            {
                                JUtil.LogMessage(this, "Warning - {0}", e);
                            }

                        }

                        // Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts.
                        foreach (string value in moduleConfig.GetValues("extraFont"))
                        {
                            fontTexture.Add(LoadFont(this, internalProp, value));
                        }

                        break;
                    }
                }

                JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count);

                textRenderer = new TextRenderer(fontTexture, new Vector2((float)fontLetterWidth, (float)fontLetterHeight), fontDefinitionString, 17, screenPixelWidth, screenPixelHeight);

                // Load our state from storage...
                persistentVarName = "activePage" + internalProp.propID;
                int activePageID = rpmComp.GetPersistentVariable(persistentVarName, pages.Count, false).MassageToInt();
                if (activePageID < pages.Count)
                {
                    activePage = pages[activePageID];
                }
                activePage.Active(true);

                // If we have global buttons, set them up.
                if (!string.IsNullOrEmpty(globalButtons))
                {
                    string[] tokens = globalButtons.Split(',');
                    for (int i = 0; i < tokens.Length; i++)
                    {
                        string buttonName = tokens[i].Trim();
                        // Notice that holes in the global button list ARE legal.
                        if (!string.IsNullOrEmpty(buttonName))
                            SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease);
                    }
                }

                audioOutput = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false);

                if (needsElectricCharge)
                {
                    del = (Action<bool>)Delegate.CreateDelegate(typeof(Action<bool>), this, "ResourceDepletedCallback");
                    rpmComp.RegisterResourceCallback(resourceName, del);
                }

                // And if the try block never completed, startupComplete will never be true.
                startupComplete = true;
            }
            catch
            {
                JUtil.AnnoyUser(this);
                // We can also disable ourselves, that should help.
                enabled = false;
                // And now that we notified the user that config is borked, we rethrow the exception so that
                // it gets logged and we can debug.
                throw;
            }
        }
		public void Start()
		{

			// If we're not in the correct location, there's no point doing anything.
			if (!InstallationPathWarning.Warn())
				return;

			try {

				// Install the calculator module.
				comp = RasterPropMonitorComputer.Instantiate(internalProp);
				comp.UpdateRefreshRates(refreshTextRate, refreshDataRate);

				// Loading the font...
				fontTexture.Add(LoadFont(this, internalProp, fontTransform, false));

				// Damn KSP's config parser!!!
				if (!string.IsNullOrEmpty(emptyColor))
					emptyColorValue = ConfigNode.ParseColor32(emptyColor);
				if (!string.IsNullOrEmpty(defaultFontTint))
					defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint);

				if (!string.IsNullOrEmpty(fontDefinition)) {
					JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition);
					fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0];
				}

				// We can pre-compute the rectangles the font characters will be copied from, this seems to make it slightly quicker...
				// although I'm not sure I'm not seeing things by this point.
				int fontLettersX = (fontTexture[0].width / fontLetterWidth);
				int fontLettersY = (fontTexture[0].height / fontLetterHeight);
				float letterSpanX = 1f / fontLettersX;
				float letterSpanY = 1f / fontLettersY;
				int lastCharacter = fontLettersX * fontLettersY;

				if (lastCharacter != fontDefinitionString.Length) {
					JUtil.LogMessage(this, "Warning, number of letters in the font definition does not match font bitmap size.");
				}

				for (int i = 0; i < lastCharacter && i < fontDefinitionString.Length; i++) {
					int xSource = i % fontLettersX;
					int ySource = (i - xSource) / fontLettersX;
					if (!fontCharacters.ContainsKey(fontDefinitionString[i]))
						fontCharacters[fontDefinitionString[i]] = new Rect(letterSpanX * xSource, letterSpanY * (fontLettersY - ySource - 1), letterSpanX, letterSpanY);
				}

				// And a little optimisation for superscript/subscript:
				fontLetterHalfHeight = fontLetterHeight / 2f;
				fontLetterHalfWidth = fontLetterWidth / 2f;
				fontLetterDoubleWidth = fontLetterWidth * 2f;

				// Now that is done, proceed to setting up the screen.

				screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32);
				screenMat = internalProp.FindModelTransform(screenTransform).renderer.material;
				foreach (string layerID in textureLayerID.Split())
					screenMat.SetTexture(layerID.Trim(), screenTexture);

				if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes())) {
					noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false);
				}

				// Create camera instance...
				cameraStructure = new FlyingCamera(part, screenTexture, cameraAspect);

				// The neat trick. IConfigNode doesn't work. No amount of kicking got it to work.
				// Well, we don't need it. GameDatabase, gimme config nodes for all props!
				foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes ("PROP")) {
					// Now, we know our own prop name.
					if (node.GetValue("name") == internalProp.propName) {
						// So this is the configuration of our prop in memory. Nice place.
						// We know it contains at least one MODULE node, us.
						// And we know our moduleID, which is the number in order of being listed in the prop.
						// Therefore the module by that number is our module's own config node.

						ConfigNode moduleConfig = node.GetNodes("MODULE")[moduleID];
						ConfigNode[] pageNodes = moduleConfig.GetNodes("PAGE");

						// Which we can now parse for page definitions.
						for (int i = 0; i < pageNodes.Length; i++) {
							// Mwahahaha.
							try {
								var newPage = new MonitorPage(i, pageNodes[i], this);
								activePage = activePage ?? newPage;
								if (newPage.isDefault)
									activePage = newPage;
								pages.Add(newPage);
							} catch (ArgumentException e) {
								JUtil.LogMessage(this, "Warning - {0}", e);
							}
							
						}

						// Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts.
						foreach (string value in moduleConfig.GetValues("extraFont")) {
							fontTexture.Add(LoadFont(this, internalProp, value, true));
						}

						break;
					}
				}
				JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count);

				// Load our state from storage...
				persistentVarName = "activePage" + internalProp.propID;
				persistence = new PersistenceAccessor(part);
				int? activePageID = persistence.GetVar(persistentVarName);
				if (activePageID != null && activePageID.Value < pages.Count) {
					activePage = pages[activePageID.Value];
				}
				activePage.Active(true);

				// If we have global buttons, set them up.
				if (!string.IsNullOrEmpty(globalButtons)) {
					string[] tokens = globalButtons.Split(',');
					for (int i = 0; i < tokens.Length; i++) {
						string buttonName = tokens[i].Trim();
						// Notice that holes in the global button list ARE legal.
						if (!string.IsNullOrEmpty(buttonName))
							SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease);
					}
				}

				audioOutput = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false);

				// One last thing to make sure of: If our pod is transparent, we're always active.
				ourPodIsTransparent = JUtil.IsPodTransparent(part);

				// And if the try block never completed, startupComplete will never be true.
				startupComplete = true;
			} catch {
				JUtil.AnnoyUser(this);
				startupFailed = true;
				// We can also disable ourselves, that should help.
				enabled = false;
				// And now that we notified the user that config is borked, we rethrow the exception so that
				// it gets logged and we can debug.
				throw;
			}

		}
        public bool RenderCamera(RenderTexture screen, float cameraAspect)
        {
            // Just in case.
            if (HighLogic.LoadedSceneIsEditor)
            {
                return false;
            }

            if (cameras.Count < 1)
            {
                return false;
            }

            var activeCamera = cameras[currentCamera];
            if (string.IsNullOrEmpty(activeCamera.cameraTransform))
            {
                return false;
            }

            if (cameraObject == null)
            {
                cameraObject = new FlyingCamera(part, cameraAspect);
                cameraObject.PointCamera(activeCamera.cameraTransform, activeCamera.currentFoV);
            }

            cameraObject.FOV = activeCamera.currentFoV;

            if (rentexWidth == 0)
            {
                rentexWidth = screen.width;
                rentexHeight = screen.height;

                // Note to self: when rentex dims != screen dims, the FOV seems to be wrong (like FOV is smaller).
            }
            if (renderTex == null)
            {
                renderTex = new RenderTexture(rentexWidth, rentexHeight, screen.depth);
                renderTex.Create();
            }

            // Negate pitch - the camera object treats a negative pitch as "up"
            if (cameraObject.Render(renderTex, activeCamera.currentYaw, -activeCamera.currentPitch))
            {
                if (cameraEffectMaterial != null)
                {
                    cameraEffectMaterial.SetVector("_ImageDims", new Vector4((float)renderTex.width, (float)renderTex.height, 1.0f / (float)renderTex.width, 1.0f / (float)renderTex.height));

                    for (int i = 0; i < ceVariables.Count; ++i)
                    {
                        float value = ceVariables[i].value.AsFloat();
                        cameraEffectMaterial.SetFloat(ceVariables[i].variable, value);
                    }

                    Graphics.Blit(renderTex, screen, cameraEffectMaterial);
                }
                else
                {
                    Graphics.Blit(renderTex, screen);
                }
                renderTex.DiscardContents();

                ITargetable target = FlightGlobals.fetch.VesselTarget;

                bool drawSomething = ((gizmoTexture != null && target != null && showTargetIcon) || homeCrosshairMaterial.color.a > 0);

                if (drawSomething)
                {
                    GL.PushMatrix();
                    GL.LoadPixelMatrix(0, screen.width, screen.height, 0);
                }

                if (gizmoTexture != null && target != null && showTargetIcon)
                {
                    // Figure out which direction the target is.
                    Vector3 targetDisplacement = target.GetTransform().position - cameraObject.GetTransform().position;
                    targetDisplacement.Normalize();

                    // Transform it using the active camera's rotation.
                    var targetDisp = GetNormalizedScreenPosition(activeCamera, targetDisplacement, cameraAspect);

                    var iconCenter = new Vector2(screen.width * targetDisp.x, screen.height * targetDisp.y);

                    // Apply some clamping values to force the icon to stay on screen
                    iconCenter.x = Math.Max(iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, iconCenter.x);
                    iconCenter.y = Math.Max(iconPixelSize * 0.5f, iconCenter.y);
                    iconCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, iconCenter.y);

                    var position = new Rect(iconCenter.x - iconPixelSize * 0.5f, iconCenter.y - iconPixelSize * 0.5f, iconPixelSize, iconPixelSize);

                    Graphics.DrawTexture(position, gizmoTexture, GizmoIcons.GetIconLocation(GizmoIcons.IconType.TARGETPLUS), 0, 0, 0, 0, iconMaterial);
                }

                if (homeCrosshairMaterial.color.a > 0)
                {
                    // Mihara: Reference point cameras are different enough to warrant it.
                    var cameraForward = cameraObject.GetTransformForward();
                    var crossHairCenter = GetNormalizedScreenPosition(activeCamera, cameraForward, cameraAspect);
                    crossHairCenter.x *= screen.width;
                    crossHairCenter.y *= screen.height;
                    crossHairCenter.x = Math.Max(iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.x = Math.Min(screen.width - iconPixelSize * 0.5f, crossHairCenter.x);
                    crossHairCenter.y = Math.Max(iconPixelSize * 0.5f, crossHairCenter.y);
                    crossHairCenter.y = Math.Min(screen.height - iconPixelSize * 0.5f, crossHairCenter.y);

                    float zoomAdjustedIconSize = iconPixelSize * Mathf.Tan(Mathf.Deg2Rad * activeCamera.fovLimits.y * 0.5f) / Mathf.Tan(Mathf.Deg2Rad * activeCamera.currentFoV * 0.5f);

                    homeCrosshairMaterial.SetPass(0);
                    GL.Begin(GL.LINES);
                    GL.Vertex3(crossHairCenter.x - zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x + zoomAdjustedIconSize * 0.5f, crossHairCenter.y, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y - zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.Vertex3(crossHairCenter.x, crossHairCenter.y + zoomAdjustedIconSize * 0.5f, 0.0f);
                    GL.End();
                }

                if (drawSomething)
                {
                    GL.PopMatrix();
                }

                return true;
            }
            else if (skipMissingCameras)
            {
                // This will handle cameras getting ejected while in use.
                SelectNextCamera();
            }

            return false;
        }
        public void Start()
        {
            InstallationPathWarning.Warn();

            // Install the calculator module.
            comp = RasterPropMonitorComputer.Instantiate(internalProp);
            comp.UpdateRefreshRates(refreshTextRate, refreshDataRate);

            // Loading the font...
            fontTexture.Add(LoadFont(this, internalProp, fontTransform, false));

            // Damn KSP's config parser!!!
            if (!string.IsNullOrEmpty(emptyColor))
            {
                emptyColorValue = ConfigNode.ParseColor32(emptyColor);
            }
            if (!string.IsNullOrEmpty(defaultFontTint))
            {
                defaultFontTintValue = ConfigNode.ParseColor32(defaultFontTint);
            }

            if (!string.IsNullOrEmpty(fontDefinition))
            {
                JUtil.LogMessage(this, "Loading font definition from {0}", fontDefinition);
                fontDefinitionString = File.ReadAllLines(KSPUtil.ApplicationRootPath + "GameData/" + fontDefinition.EnforceSlashes(), Encoding.UTF8)[0];
            }

            // We can pre-compute the rectangles the font characters will be copied from, this seems to make it slightly quicker...
            // although I'm not sure I'm not seeing things by this point.
            int   fontLettersX  = (fontTexture[0].width / fontLetterWidth);
            int   fontLettersY  = (fontTexture[0].height / fontLetterHeight);
            float letterSpanX   = 1f / fontLettersX;
            float letterSpanY   = 1f / fontLettersY;
            int   lastCharacter = fontLettersX * fontLettersY;

            if (lastCharacter != fontDefinitionString.Length)
            {
                JUtil.LogMessage(this, "Warning, number of letters in the font definition does not match font bitmap size.");
            }

            for (int i = 0; i < lastCharacter && i < fontDefinitionString.Length; i++)
            {
                int xSource = i % fontLettersX;
                int ySource = (i - xSource) / fontLettersX;
                if (!fontCharacters.ContainsKey(fontDefinitionString[i]))
                {
                    fontCharacters[fontDefinitionString[i]] = new Rect(letterSpanX * xSource, letterSpanY * (fontLettersY - ySource - 1), letterSpanX, letterSpanY);
                }
            }

            // And a little optimisation for superscript/subscript:
            fontLetterHalfHeight  = fontLetterHeight / 2f;
            fontLetterHalfWidth   = fontLetterWidth / 2f;
            fontLetterDoubleWidth = fontLetterWidth * 2f;

            // Now that is done, proceed to setting up the screen.

            screenTexture = new RenderTexture(screenPixelWidth, screenPixelHeight, 24, RenderTextureFormat.ARGB32);
            screenMat     = internalProp.FindModelTransform(screenTransform).renderer.material;
            foreach (string layerID in textureLayerID.Split())
            {
                screenMat.SetTexture(layerID.Trim(), screenTexture);
            }

            if (GameDatabase.Instance.ExistsTexture(noSignalTextureURL.EnforceSlashes()))
            {
                noSignalTexture = GameDatabase.Instance.GetTexture(noSignalTextureURL.EnforceSlashes(), false);
            }

            // Create camera instance...
            cameraStructure = new FlyingCamera(part, screenTexture, cameraAspect);

            // The neat trick. IConfigNode doesn't work. No amount of kicking got it to work.
            // Well, we don't need it. GameDatabase, gimme config nodes for all props!
            foreach (ConfigNode node in GameDatabase.Instance.GetConfigNodes("PROP"))
            {
                // Now, we know our own prop name.
                if (node.GetValue("name") == internalProp.propName)
                {
                    // So this is the configuration of our prop in memory. Nice place.
                    // We know it contains at least one MODULE node, us.
                    // And we know our moduleID, which is the number in order of being listed in the prop.
                    // Therefore the module by that number is our module's own config node.

                    ConfigNode   moduleConfig = node.GetNodes("MODULE")[moduleID];
                    ConfigNode[] pageNodes    = moduleConfig.GetNodes("PAGE");

                    // Which we can now parse for page definitions.
                    for (int i = 0; i < pageNodes.Length; i++)
                    {
                        // Mwahahaha.
                        try {
                            var newPage = new MonitorPage(i, pageNodes[i], this);
                            activePage = activePage ?? newPage;
                            if (newPage.isDefault)
                            {
                                activePage = newPage;
                            }
                            pages.Add(newPage);
                        } catch (ArgumentException e) {
                            JUtil.LogMessage(this, "Warning - {0}", e);
                        }
                    }

                    // Now that all pages are loaded, we can use the moment in the loop to suck in all the extra fonts.
                    foreach (string value in moduleConfig.GetValues("extraFont"))
                    {
                        fontTexture.Add(LoadFont(this, internalProp, value, true));
                    }

                    break;
                }
            }
            JUtil.LogMessage(this, "Done setting up pages, {0} pages ready.", pages.Count);

            // Load our state from storage...
            persistentVarName = "activePage" + internalProp.propID;
            persistence       = new PersistenceAccessor(part);
            int?activePageID = persistence.GetVar(persistentVarName);

            if (activePageID != null && activePageID.Value < pages.Count)
            {
                activePage = pages[activePageID.Value];
            }
            activePage.Active(true);

            // If we have global buttons, set them up.
            if (!string.IsNullOrEmpty(globalButtons))
            {
                string[] tokens = globalButtons.Split(',');
                for (int i = 0; i < tokens.Length; i++)
                {
                    string buttonName = tokens[i].Trim();
                    // Notice that holes in the global button list ARE legal.
                    if (!string.IsNullOrEmpty(buttonName))
                    {
                        SmarterButton.CreateButton(internalProp, buttonName, i, GlobalButtonClick, GlobalButtonRelease);
                    }
                }
            }

            audioOutput     = JUtil.SetupIVASound(internalProp, buttonClickSound, buttonClickVolume, false);
            startupComplete = true;
        }