// Analysis disable once UnusedParameter public bool RenderOrbit(RenderTexture screen, float cameraAspect) { if (!startupComplete || HighLogic.LoadedSceneIsEditor) { return(false); } // Make sure the parameters fit on the screen. Vector4 displayPosition = orbitDisplayPosition; displayPosition.z = Mathf.Min(screen.width - displayPosition.x, displayPosition.z); displayPosition.w = Mathf.Min(screen.height - displayPosition.y, displayPosition.w); // Here is our pixel budget in each direction: double horizPixelSize = displayPosition.z - iconPixelSize; double vertPixelSize = displayPosition.w - iconPixelSize; // Find a basis for transforming values into the framework of // vessel.orbit. The rendering framework assumes the periapsis // is drawn directly to the right of the mainBody center of mass. // It assumes the orbit's prograde direction is "up" (screen // relative) at the periapsis, providing a counter-clockwise // motion for vessel. // Once we have the basic transform, we will add in scalars // that will ultimately transform an arbitrary point (relative to // the planet's center) into screen space. Matrix4x4 screenTransform = Matrix4x4.identity; double now = Planetarium.GetUniversalTime(); double timeAtPe = vessel.orbit.NextPeriapsisTime(now); // Get the 3 direction vectors, based on Pe being on the right of the screen // OrbitExtensions provides handy utilities to get these. Vector3d right = vessel.orbit.Up(timeAtPe); Vector3d forward = vessel.orbit.SwappedOrbitNormal(); // MOARdV: OrbitExtensions.Horizontal is unstable. I've seen it // become (0, 0, 0) intermittently in flight. Instead, use the // cross product of the other two. // We flip the sign of this vector because we are using an inverted // y coordinate system to keep the icons right-side up. Vector3d up = -Vector3d.Cross(forward, right); //Vector3d up = -vessel.orbit.Horizontal(timeAtPe); screenTransform.SetRow(0, new Vector4d(right.x, right.y, right.z, 0.0)); screenTransform.SetRow(1, new Vector4d(up.x, up.y, up.z, 0.0)); screenTransform.SetRow(2, new Vector4d(forward.x, forward.y, forward.z, 0.0)); // Figure out our bounds. First, make sure the entire planet // fits on the screen. We define the center of the vessel.mainBody // as the origin of our coodinate system. double maxX = vessel.mainBody.Radius; double minX = -maxX; double maxY = maxX; double minY = -maxX; if (vessel.mainBody.atmosphere) { maxX += vessel.mainBody.maxAtmosphereAltitude; minX = -maxX; maxY = maxX; minY = -maxX; } // Now make sure the entire orbit fits on the screen. Vector3 vesselPos; // The PeR, ApR, and semiMinorAxis are all one dimensional, so we // can just apply them directly to these values. maxX = Math.Max(maxX, vessel.orbit.PeR); if (vessel.orbit.eccentricity < 1.0) { minX = Math.Min(minX, -vessel.orbit.ApR); maxY = Math.Max(maxY, vessel.orbit.semiMinorAxis); minY = Math.Min(minY, -vessel.orbit.semiMinorAxis); } else if (vessel.orbit.EndUT > 0.0) { // If we're hyperbolic, let's get the SoI transition vesselPos = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(vessel.orbit.EndUT)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } // Make sure the vessel shows up on-screen. Since a hyperbolic // orbit doesn't have a meaningful ApR, we use this as a proxy for // how far we need to extend the bounds to show the vessel. vesselPos = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(now)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); // Account for a target vessel var targetBody = FlightGlobals.fetch.VesselTarget as CelestialBody; var targetVessel = FlightGlobals.fetch.VesselTarget as Vessel; if (targetVessel != null && targetVessel.mainBody != vessel.mainBody) { // We only care about tgtVessel if it is in the same SoI. targetVessel = null; } if (targetVessel != null && !targetVessel.LandedOrSplashed) { if (targetVessel.mainBody == vessel.mainBody) { double tgtPe = targetVessel.orbit.NextPeriapsisTime(now); vesselPos = screenTransform.MultiplyPoint3x4(targetVessel.orbit.SwappedRelativePositionAtUT(tgtPe)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); if (targetVessel.orbit.eccentricity < 1.0) { vesselPos = screenTransform.MultiplyPoint3x4(targetVessel.orbit.SwappedRelativePositionAtUT(targetVessel.orbit.NextApoapsisTime(now))); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } vesselPos = screenTransform.MultiplyPoint3x4(targetVessel.orbit.SwappedRelativePositionAtUT(now)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } } if (targetBody != null) { // Validate some values up front, so we don't need to test them later. if (targetBody.GetOrbit() == null) { targetBody = null; } else if (targetBody.orbit.referenceBody == vessel.orbit.referenceBody) { // If the target body orbits our current world, let's at // least make sure the body's location is visible. vesselPos = screenTransform.MultiplyPoint3x4(targetBody.GetOrbit().SwappedRelativePositionAtUT(now)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } } ManeuverNode node = (vessel.patchedConicSolver.maneuverNodes.Count > 0) ? vessel.patchedConicSolver.maneuverNodes[0] : null; if (node != null) { double nodePe = node.nextPatch.NextPeriapsisTime(now); vesselPos = screenTransform.MultiplyPoint3x4(node.nextPatch.SwappedRelativePositionAtUT(nodePe)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); if (node.nextPatch.eccentricity < 1.0) { double nodeAp = node.nextPatch.NextApoapsisTime(now); vesselPos = screenTransform.MultiplyPoint3x4(node.nextPatch.SwappedRelativePositionAtUT(nodeAp)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } else if (node.nextPatch.EndUT > 0.0) { // If the next patch is hyperbolic, include the endpoint. vesselPos = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(node.nextPatch.EndUT)); maxX = Math.Max(maxX, vesselPos.x); minX = Math.Min(minX, vesselPos.x); maxY = Math.Max(maxY, vesselPos.y); minY = Math.Min(minY, vesselPos.y); } } // Add translation. This will ensure that all of the features // under consideration above will be displayed. screenTransform[0, 3] = -0.5f * (float)(maxX + minX); screenTransform[1, 3] = -0.5f * (float)(maxY + minY); double neededWidth = maxX - minX; double neededHeight = maxY - minY; // Pick a scalar that will fit the bounding box we just created. float pixelScalar = (float)Math.Min(horizPixelSize / neededWidth, vertPixelSize / neededHeight); screenTransform = Matrix4x4.Scale(new Vector3(pixelScalar, pixelScalar, pixelScalar)) * screenTransform; GL.Clear(true, true, backgroundColorValue); GL.PushMatrix(); GL.LoadPixelMatrix(-displayPosition.z * 0.5f, displayPosition.z * 0.5f, displayPosition.w * 0.5f, -displayPosition.w * 0.5f); GL.Viewport(new Rect(displayPosition.x, screen.height - displayPosition.y - displayPosition.w, displayPosition.z, displayPosition.w)); lineMaterial.SetPass(0); GL.Begin(GL.LINES); // Draw the planet: Vector3 focusCenter = screenTransform.MultiplyPoint3x4(new Vector3(0.0f, 0.0f, 0.0f)); // orbitDriver is null on the sun, so we'll just use white instead. GL.Color((vessel.mainBody.orbitDriver == null) ? new Color(1.0f, 1.0f, 1.0f) : vessel.mainBody.orbitDriver.orbitColor); DrawCircle(focusCenter.x, focusCenter.y, (float)(vessel.mainBody.Radius * pixelScalar), orbitPoints); if (vessel.mainBody.atmosphere) { // Use the atmospheric ambient to color the atmosphere circle. GL.Color(vessel.mainBody.atmosphericAmbientColor); DrawCircle(focusCenter.x, focusCenter.y, (float)((vessel.mainBody.Radius + vessel.mainBody.maxAtmosphereAltitude) * pixelScalar), orbitPoints); } if (targetVessel != null && !targetVessel.LandedOrSplashed) { GL.Color(iconColorTargetValue); if (!targetVessel.orbit.activePatch && targetVessel.orbit.eccentricity < 1.0 && targetVessel.orbit.referenceBody == vessel.orbit.referenceBody) { // For some reason, activePatch is false for targetVessel. // If we have a stable orbit for the target, use a fallback // rendering method: ReallyDrawOrbit(targetVessel.orbit, vessel.orbit.referenceBody, screenTransform, orbitPoints); } else { DrawOrbit(targetVessel.orbit, vessel.orbit.referenceBody, screenTransform, orbitPoints); } } foreach (CelestialBody moon in vessel.orbit.referenceBody.orbitingBodies) { if (moon != targetBody) { GL.Color(moon.orbitDriver.orbitColor); ReallyDrawOrbit(moon.GetOrbit(), vessel.orbit.referenceBody, screenTransform, orbitPoints); } } if (targetBody != null) { GL.Color(iconColorTargetValue); ReallyDrawOrbit(targetBody.GetOrbit(), vessel.orbit.referenceBody, screenTransform, orbitPoints); } if (node != null) { GL.Color(orbitColorNextNodeValue); DrawOrbit(node.nextPatch, vessel.orbit.referenceBody, screenTransform, orbitPoints); } if (vessel.orbit.nextPatch != null && vessel.orbit.nextPatch.activePatch) { GL.Color(orbitColorNextNodeValue); DrawOrbit(vessel.orbit.nextPatch, vessel.orbit.referenceBody, screenTransform, orbitPoints); } // Draw the vessel orbit GL.Color(orbitColorSelfValue); DrawOrbit(vessel.orbit, vessel.orbit.referenceBody, screenTransform, orbitPoints); // Done drawing lines. Reset color to white, so we don't mess up anyone else. GL.Color(Color.white); GL.End(); // Draw target vessel icons. Vector3 transformedPosition; foreach (CelestialBody moon in vessel.orbit.referenceBody.orbitingBodies) { if (moon != targetBody) { transformedPosition = screenTransform.MultiplyPoint3x4(moon.getTruePositionAtUT(now) - vessel.orbit.referenceBody.getTruePositionAtUT(now)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, moon.orbitDriver.orbitColor, MapIcons.OtherIcon.PLANET); } } if (targetVessel != null || targetBody != null) { var orbit = (targetVessel != null) ? targetVessel.GetOrbit() : targetBody.GetOrbit(); double tClosestApproach, dClosestApproach; if (targetVessel != null && targetVessel.LandedOrSplashed) { orbit = JUtil.ClosestApproachSrfOrbit(vessel.orbit, targetVessel, out tClosestApproach, out dClosestApproach); } else { dClosestApproach = JUtil.GetClosestApproach(vessel.orbit, orbit, out tClosestApproach); DrawNextPe(orbit, vessel.orbit.referenceBody, now, iconColorTargetValue, screenTransform); DrawNextAp(orbit, vessel.orbit.referenceBody, now, iconColorTargetValue, screenTransform); } if (targetBody != null) { transformedPosition = screenTransform.MultiplyPoint3x4(targetBody.getTruePositionAtUT(now) - vessel.orbit.referenceBody.getTruePositionAtUT(now)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, iconColorTargetValue, MapIcons.OtherIcon.PLANET); } else { transformedPosition = screenTransform.MultiplyPoint3x4(orbit.SwappedRelativePositionAtUT(now)); DrawIcon(transformedPosition.x, transformedPosition.y, targetVessel.vesselType, iconColorTargetValue); } if (vessel.orbit.AscendingNodeExists(orbit)) { double anTime = vessel.orbit.TimeOfAscendingNode(orbit, now); if (anTime < vessel.orbit.EndUT || (vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ESCAPE && vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ENCOUNTER)) { transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(anTime)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorSelfValue, MapIcons.OtherIcon.AN); } } if (vessel.orbit.DescendingNodeExists(orbit)) { double dnTime = vessel.orbit.TimeOfDescendingNode(orbit, now); if (dnTime < vessel.orbit.EndUT || (vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ESCAPE && vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ENCOUNTER)) { transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(dnTime)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorSelfValue, MapIcons.OtherIcon.DN); } } Orbit o = GetPatchAtUT(vessel.orbit, tClosestApproach); if (o != null) { Vector3d encounterPosition = o.SwappedRelativePositionAtUT(tClosestApproach) + o.referenceBody.getTruePositionAtUT(tClosestApproach) - vessel.orbit.referenceBody.getTruePositionAtUT(tClosestApproach); transformedPosition = screenTransform.MultiplyPoint3x4(encounterPosition); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, iconColorClosestApproachValue, MapIcons.OtherIcon.SHIPATINTERCEPT); } // Unconditionally try to draw the closest approach point on // the target orbit. transformedPosition = screenTransform.MultiplyPoint3x4(orbit.SwappedRelativePositionAtUT(tClosestApproach)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, iconColorClosestApproachValue, MapIcons.OtherIcon.TGTATINTERCEPT); } else { if (vessel.orbit.AscendingNodeEquatorialExists()) { double anTime = vessel.orbit.TimeOfAscendingNodeEquatorial(now); if (anTime < vessel.orbit.EndUT || (vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ESCAPE && vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ENCOUNTER)) { transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(anTime)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorSelfValue, MapIcons.OtherIcon.AN); } } if (vessel.orbit.DescendingNodeEquatorialExists()) { double dnTime = vessel.orbit.TimeOfDescendingNodeEquatorial(now); if (dnTime < vessel.orbit.EndUT || (vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ESCAPE && vessel.orbit.patchEndTransition != Orbit.PatchTransitionType.ENCOUNTER)) { transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(dnTime)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorSelfValue, MapIcons.OtherIcon.DN); } } } // Draw orbital features DrawNextPe(vessel.orbit, vessel.orbit.referenceBody, now, iconColorPEValue, screenTransform); DrawNextAp(vessel.orbit, vessel.orbit.referenceBody, now, iconColorAPValue, screenTransform); if (vessel.orbit.nextPatch != null && vessel.orbit.nextPatch.activePatch) { transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(vessel.orbit.EndUT)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorSelfValue, MapIcons.OtherIcon.EXITSOI); Orbit nextPatch = vessel.orbit.nextPatch.nextPatch; if (nextPatch != null && nextPatch.activePatch) { transformedPosition = screenTransform.MultiplyPoint3x4(nextPatch.SwappedRelativePositionAtUT(nextPatch.EndUT) + nextPatch.referenceBody.getTruePositionAtUT(nextPatch.EndUT) - vessel.orbit.referenceBody.getTruePositionAtUT(nextPatch.EndUT)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorNextNodeValue, MapIcons.OtherIcon.EXITSOI); } } if (node != null && node.nextPatch.activePatch) { DrawNextPe(node.nextPatch, vessel.orbit.referenceBody, now, orbitColorNextNodeValue, screenTransform); DrawNextAp(node.nextPatch, vessel.orbit.referenceBody, now, orbitColorNextNodeValue, screenTransform); transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(node.UT)); DrawIcon(transformedPosition.x, transformedPosition.y, VesselType.Unknown, orbitColorNextNodeValue, MapIcons.OtherIcon.NODE); } // Draw ownship icon transformedPosition = screenTransform.MultiplyPoint3x4(vessel.orbit.SwappedRelativePositionAtUT(now)); DrawIcon(transformedPosition.x, transformedPosition.y, vessel.vesselType, iconColorSelfValue); GL.PopMatrix(); GL.Viewport(new Rect(0, 0, screen.width, screen.height)); return(true); }