public void RenderMarkers(DisposableIterator apsis_iterator, MapObject.ObjectType type, NodeSource source, Vessel vessel, CelestialBody celestial) { // We render at most 64 markers of one type and one provenance (e.g., at // most 64 perilunes for the prediction of the active vessel). This is // more than is readable, and keeps the size of the map node pool under // control. for (int i = 0; i < 64 && !apsis_iterator.IteratorAtEnd(); ++i, apsis_iterator.IteratorIncrement()) { QP apsis = apsis_iterator.IteratorGetDiscreteTrajectoryQP(); MapNodeProperties node_properties = new MapNodeProperties { visible = true, object_type = type, vessel = vessel, celestial = celestial, world_position = (Vector3d)apsis.q, velocity = (Vector3d)apsis.p, source = source, time = apsis_iterator.IteratorGetDiscreteTrajectoryTime() }; if (pool_index_ == nodes_.Count) { nodes_.Add(MakePoolNode()); } else if (properties_[nodes_[pool_index_]].object_type != type || properties_[nodes_[pool_index_]].source != source) { // KSP attaches labels to its map nodes, but never detaches them. // If the node changes type, we end up with an arbitrary combination of // labels Ap, Pe, AN, DN. // If the node changes source, the colour of the icon label is not // updated to match the icon (making it unreadable in some cases). // Recreating the node entirely takes a long time (approximately // 𝑁 * 70 μs, where 𝑁 is the total number of map nodes in existence), // instead we manually get rid of the labels. foreach (var component in nodes_[pool_index_].transform.GetComponentsInChildren < TMPro.TextMeshProUGUI>()) { if (component.name == "iconLabel(Clone)") { UnityEngine.Object.Destroy(component.gameObject); } } // Ensure that KSP thinks the type changed, and reattaches icon // labels next time around, otherwise we might end up with no labels. // Null nodes do not have a label, so inducing a type change through // Null does not result in spurious labels. properties_[nodes_[pool_index_]].object_type = MapObject.ObjectType.Null; nodes_[pool_index_].NodeUpdate(); } properties_[nodes_[pool_index_++]] = node_properties; } }
public void RenderMarkers(DisposableIterator apsis_iterator, MapObject.ObjectType type, NodeSource source, ReferenceFrameSelector reference_frame) { MapObject associated_map_object; UnityEngine.Color colour; switch (type) { case MapObject.ObjectType.Apoapsis: case MapObject.ObjectType.Periapsis: CelestialBody fixed_body = reference_frame.selected_celestial; associated_map_object = fixed_body.MapObject; colour = fixed_body.orbit == null ? XKCDColors.SunshineYellow : fixed_body.orbitDriver.Renderer.nodeColor; break; case MapObject.ObjectType.ApproachIntersect: associated_map_object = reference_frame.target_override.mapObject; colour = XKCDColors.Chartreuse; break; case MapObject.ObjectType.AscendingNode: case MapObject.ObjectType.DescendingNode: if (!reference_frame.target_override && (reference_frame.frame_type == ReferenceFrameSelector.FrameType.BODY_CENTRED_NON_ROTATING || reference_frame.frame_type == ReferenceFrameSelector.FrameType.BODY_SURFACE)) { // In one-body frames, the apsides are shown with the colour of the // body. // The nodes are with respect to the equator, rather than with respect // to an orbit. We show the nodes in a different (but arbitrary) // colour so that they can be distinguished easily. associated_map_object = reference_frame.selected_celestial.MapObject; colour = XKCDColors.Chartreuse; } else { // In two-body frames, if apsides are shown, they are shown with the // colour of the secondary (or in XKCD chartreuse if the secondary is // a vessel). // The nodes are with respect to the orbit of the secondary around the // primary. We show the nodes with the colour of the primary. CelestialBody primary = reference_frame.target_override ? reference_frame.selected_celestial : reference_frame.selected_celestial.referenceBody; associated_map_object = primary.MapObject; colour = primary.orbit == null ? XKCDColors.SunshineYellow : primary.orbitDriver.Renderer.nodeColor; } break; default: throw Log.Fatal($"Unexpected type {type}"); } colour.a = 1; for (int i = 0; i < MaxRenderedNodes && !apsis_iterator.IteratorAtEnd(); ++i, apsis_iterator.IteratorIncrement()) { QP apsis = apsis_iterator.IteratorGetDiscreteTrajectoryQP(); MapNodeProperties node_properties = new MapNodeProperties { visible = true, object_type = type, colour = colour, reference_frame = reference_frame, world_position = (Vector3d)apsis.q, velocity = (Vector3d)apsis.p, source = source, time = apsis_iterator.IteratorGetDiscreteTrajectoryTime(), associated_map_object = associated_map_object, }; if (type == MapObject.ObjectType.Periapsis && reference_frame.selected_celestial.GetAltitude( node_properties.world_position) < 0) { node_properties.object_type = MapObject.ObjectType.PatchTransition; node_properties.colour = XKCDColors.Orange; } if (pool_index_ == nodes_.Count) { nodes_.Add(MakePoolNode()); } else if (properties_[nodes_[pool_index_]].object_type != node_properties.object_type || properties_[nodes_[pool_index_]].colour != node_properties.colour) { // KSP attaches labels to its map nodes, but never detaches them. // If the node changes type, we end up with an arbitrary combination of // labels Ap, Pe, AN, DN. // Similarly, if the node changes colour, the colour of the icon label // is not updated to match the icon (making it unreadable in some // cases). Recreating the node entirely takes a long time // (approximately 𝑁 * 70 μs, where 𝑁 is the total number of map nodes // in existence), instead we manually get rid of the labels. foreach (var component in nodes_[pool_index_].transform.GetComponentsInChildren < TMPro.TextMeshProUGUI>()) { if (component.name == "iconLabel(Clone)") { UnityEngine.Object.Destroy(component.gameObject); } } // Ensure that KSP thinks the type changed, and reattaches icon // labels next time around, otherwise we might end up with no labels. // Null nodes do not have a label, so inducing a type change through // Null does not result in spurious labels. Note that the type is // updated only if the node is visible. properties_[nodes_[pool_index_]].visible = true; properties_[nodes_[pool_index_]].object_type = MapObject.ObjectType.Null; nodes_[pool_index_].NodeUpdate(); } properties_[nodes_[pool_index_++]] = node_properties; } }
private KSP.UI.Screens.Mapview.MapNode MakePoolNode() { var new_node = KSP.UI.Screens.Mapview.MapNode.Create( "apsis", // If we see this colour, something has gone wrong. XKCDColors.Pale, pixelSize: 32, hoverable: true, pinnable: true, blocksInput: true); new_node.OnClick += (KSP.UI.Screens.Mapview.MapNode node, Mouse.Buttons buttons) => { if (buttons == Mouse.Buttons.Left) { MapNodeProperties properties = properties_[node]; if (PlanetariumCamera.fetch.target != properties.associated_map_object) { PlanetariumCamera.fetch.SetTarget( properties.associated_map_object); } } }; new_node.OnUpdateVisible += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.IconData icon) => { icon.visible = properties_[node].visible; icon.color = properties_[node].colour; }; new_node.OnUpdateType += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.TypeData type) => { MapNodeProperties properties = properties_[node]; type.oType = properties.object_type; switch (properties.object_type) { case MapObject.ObjectType.PatchTransition: type.pType = KSP.UI.Screens.Mapview.MapNode.PatchTransitionNodeType.Impact; break; case MapObject.ObjectType.ApproachIntersect: type.aType = KSP.UI.Screens.Mapview.MapNode.ApproachNodeType. CloseApproachOwn; break; } }; new_node.OnUpdateCaption += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.CaptionData caption) => { var properties = properties_[node]; string source; switch (properties.source) { case NodeSource.FlightPlan: source = "Planned"; break; case NodeSource.Prediction: source = "Predicted"; break; default: throw Log.Fatal($"Unexpected node source {properties.source}"); } switch (properties.object_type) { case MapObject.ObjectType.Periapsis: case MapObject.ObjectType.Apoapsis: { string apsis_name = properties.object_type == MapObject.ObjectType.Periapsis ? "Periapsis" : "Apoapsis"; CelestialBody celestial = properties.reference_frame.selected_celestial; Vector3d position = properties.world_position; double speed = properties.velocity.magnitude; caption.Header = $@"{source} {celestial.name} {apsis_name} :\n{ celestial.GetAltitude(position):N0} m".ToString( Culture.culture); caption.captionLine2 = $"{speed:N0} m/s".ToString(Culture.culture); break; } case MapObject.ObjectType.AscendingNode: case MapObject.ObjectType.DescendingNode: { string node_name = properties.object_type == MapObject.ObjectType.AscendingNode ? "Ascending Node" : "Descending Node"; string plane = properties.reference_frame.ReferencePlaneDescription(); caption.Header = $"{source} {node_name} :\n{plane}"; caption.captionLine2 = $"{properties.velocity.z:N0} m/s".ToString(Culture.culture); break; } case MapObject.ObjectType.ApproachIntersect: { Vessel target_vessel = properties.reference_frame.target_override; double separation = (target_vessel.GetWorldPos3D() - properties.world_position).magnitude; double speed = properties.velocity.magnitude; caption.Header = $@"{source} Target Approach : {separation:N0} m".ToString( Culture.culture); caption.captionLine2 = $"{speed:N0} m/s".ToString(Culture.culture); break; } case MapObject.ObjectType.PatchTransition: { CelestialBody celestial = properties.reference_frame.selected_celestial; caption.Header = $"{source} {celestial.name} Impact"; caption.captionLine1 = ""; caption.captionLine2 = ""; break; } } if (properties.object_type != MapObject.ObjectType.PatchTransition) { caption.captionLine1 = "T" + FlightPlanner.FormatTimeSpan(TimeSpan.FromSeconds( Planetarium.GetUniversalTime() - properties.time)); } }; new_node.OnUpdatePosition += (KSP.UI.Screens.Mapview.MapNode node) => ScaledSpace.LocalToScaledSpace(properties_[node].world_position); return(new_node); }
private KSP.UI.Screens.Mapview.MapNode MakePoolNode() { var new_node = KSP.UI.Screens.Mapview.MapNode.Create( "apsis", // If we see this colour, something has gone wrong. XKCDColors.Pale, pixelSize: 32, hoverable: true, pinnable: true, blocksInput: true); new_node.OnClick += (KSP.UI.Screens.Mapview.MapNode node, Mouse.Buttons buttons) => { if (buttons == Mouse.Buttons.Left) { MapNodeProperties properties = properties_[node]; if (PlanetariumCamera.fetch.target != properties.associated_map_object) { PlanetariumCamera.fetch.SetTarget( properties.associated_map_object); } } }; new_node.OnUpdateVisible += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.IconData icon) => { icon.visible = properties_[node].visible; icon.color = properties_[node].colour; }; new_node.OnUpdateType += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.TypeData type) => { MapNodeProperties properties = properties_[node]; type.oType = properties.object_type; switch (properties.object_type) { case MapObject.ObjectType.PatchTransition: type.pType = KSP.UI.Screens.Mapview.MapNode.PatchTransitionNodeType. Impact; break; case MapObject.ObjectType.ApproachIntersect: type.aType = KSP.UI.Screens.Mapview.MapNode.ApproachNodeType. CloseApproachOwn; break; } }; new_node.OnUpdateCaption += (KSP.UI.Screens.Mapview.MapNode node, KSP.UI.Screens.Mapview.MapNode.CaptionData caption) => { var properties = properties_[node]; string source; switch (properties.source) { case NodeSource.FlightPlan: source = L10N.CacheFormat("#Principia_MapNode_Planned"); break; case NodeSource.Prediction: source = L10N.CacheFormat("#Principia_MapNode_Predicted"); break; default: throw Log.Fatal($"Unexpected node source {properties.source}"); } switch (properties.object_type) { case MapObject.ObjectType.Periapsis: case MapObject.ObjectType.Apoapsis: { CelestialBody celestial = properties.reference_frame.Centre(); Vector3d position = properties.world_position; double speed = properties.velocity.magnitude; caption.Header = L10N.CelestialString( properties.object_type == MapObject.ObjectType.Periapsis ? "#Principia_MapNode_PeriapsisHeader" : "#Principia_MapNode_ApoapsisHeader", new[] { celestial }, source, celestial.GetAltitude(position).FormatN(0)); caption.captionLine2 = L10N.CacheFormat("#Principia_MapNode_ApsisCaptionLine2", speed.FormatN(0)); break; } case MapObject.ObjectType.AscendingNode: case MapObject.ObjectType.DescendingNode: { string node_name = properties.object_type == MapObject.ObjectType.AscendingNode ? L10N.CacheFormat("#Principia_MapNode_AscendingNode") : L10N.CacheFormat("#Principia_MapNode_DescendingNode"); string plane = properties.reference_frame.ReferencePlaneDescription(); caption.Header = L10N.CacheFormat("#Principia_MapNode_NodeHeader", source, node_name, plane); caption.captionLine2 = L10N.CacheFormat( "#Principia_MapNode_NodeCaptionLine2", properties.velocity.z.FormatN(0)); break; } case MapObject.ObjectType.ApproachIntersect: { double separation = (properties.reference_frame.target.GetWorldPos3D() - properties.world_position).magnitude; double speed = properties.velocity.magnitude; caption.Header = L10N.CacheFormat("#Principia_MapNode_ApproachHeader", source, separation.FormatN(0)); caption.captionLine2 = L10N.CacheFormat( "#Principia_MapNode_ApproachCaptionLine2", speed.FormatN(0)); break; } case MapObject.ObjectType.PatchTransition: { CelestialBody celestial = properties.reference_frame.Centre(); caption.Header = L10N.CacheFormat("#Principia_MapNode_ImpactHeader", source, celestial.Name()); caption.captionLine1 = ""; caption.captionLine2 = ""; break; } } if (properties.object_type != MapObject.ObjectType.PatchTransition) { caption.captionLine1 = "T" + new PrincipiaTimeSpan( Planetarium.GetUniversalTime() - properties.time). Format(with_leading_zeroes: false, with_seconds: true); } }; new_node.OnUpdatePosition += (KSP.UI.Screens.Mapview.MapNode node) => ScaledSpace.LocalToScaledSpace(properties_[node].world_position); return(new_node); }
public void RenderMarkers(DisposableIterator apsis_iterator, Provenance provenance, ReferenceFrameSelector reference_frame) { if (!nodes_.ContainsKey(provenance)) { nodes_.Add(provenance, new SingleProvenancePool()); } var pool = nodes_[provenance]; MapObject associated_map_object; UnityEngine.Color colour; switch (provenance.type) { case MapObject.ObjectType.Apoapsis: case MapObject.ObjectType.Periapsis: CelestialBody fixed_body = reference_frame.Centre(); associated_map_object = fixed_body.MapObject; colour = fixed_body.orbit == null ? XKCDColors.SunshineYellow : fixed_body.orbitDriver.Renderer.nodeColor; break; case MapObject.ObjectType.ApproachIntersect: associated_map_object = reference_frame.target.mapObject; colour = XKCDColors.Chartreuse; break; case MapObject.ObjectType.AscendingNode: case MapObject.ObjectType.DescendingNode: if (reference_frame.Centre() == null) { // In two-body frames, if apsides are shown, they are shown with the // colour of the secondary (or in XKCD chartreuse if the secondary is // a vessel). // The nodes are with respect to the orbit of the secondary around the // primary. We show the nodes with the colour of the primary. CelestialBody primary = reference_frame.OrientingBody(); associated_map_object = primary.MapObject; colour = primary.orbit == null ? XKCDColors.SunshineYellow : primary.orbitDriver.Renderer.nodeColor; } else { // In one-body frames, the apsides are shown with the colour of the // body. // The nodes are with respect to the equator, rather than with respect // to an orbit. We show the nodes in a different (but arbitrary) // colour so that they can be distinguished easily. associated_map_object = reference_frame.Centre().MapObject; colour = XKCDColors.Chartreuse; } break; default: throw Log.Fatal($"Unexpected type {provenance.type}"); } colour.a = 1; for (int i = 0; i < MaxNodesPerProvenance && !apsis_iterator.IteratorAtEnd(); ++i, apsis_iterator.IteratorIncrement()) { QP apsis = apsis_iterator.IteratorGetDiscreteTrajectoryQP(); MapNodeProperties node_properties = new MapNodeProperties { visible = true, object_type = provenance.type, colour = colour, reference_frame = reference_frame, world_position = (Vector3d)apsis.q, velocity = (Vector3d)apsis.p, source = provenance.source, time = apsis_iterator.IteratorGetDiscreteTrajectoryTime(), associated_map_object = associated_map_object, }; if (provenance.type == MapObject.ObjectType.Periapsis && reference_frame.Centre().GetAltitude( node_properties.world_position) < 0) { node_properties.object_type = MapObject.ObjectType.PatchTransition; node_properties.colour = XKCDColors.Orange; } if (pool.nodes_used == pool.nodes.Count) { pool.nodes.Add(MakePoolNode()); } properties_[pool.nodes[pool.nodes_used++]] = node_properties; } }