Juno: New Origins

Juno: New Origins

Not enough ratings
Creating a "Heads Up Display" using a MFD Panel
By mreed2
This is a relatively simple guide that covers making a heads up display that is usable with a static camera (or a Drool), Vizzy code to display a box at a particular location (given in PCI coordinates), and finally to create an "arm" assembly that can keep the camera / MFD pointing directly at the position that it marked in the MFD.

The Vizzy code is actually fairly simple, for once.
   
Award
Favorite
Favorited
Unfavorite
Creating a transparent MFD
First, you need to add a MFD to your craft. This option is found here:
Next, go to the paint mode and set the mode to "All" (instead of "Primary")
Next, pick a transparent color, as shown
In the default palette, the last two colors ("Glass Exterior" and "Glass Interior") are mostly transparent. You can also define a new color that is transparent if you wish.
Left click on the color to select it, then left click on the MFD to change all of its color settings to match the selected color. If it worked properly, the MFD should turn... Transparent. :)
If the MFD only partially turned transparent, you didn't change the mode to "All". It took me a disappointingly long time to figure this out.
Creating a camera and pointing it at the MFD
For the purpose of the rest of this guide, I'm going to assume you have something that looks like this:
Your exact solution doesn't matter -- what's important that you have a camera (named "Camera") that is pointed at the MFD (named "Multi-function Display"), and the camera is positioned so that it points through the center of the panel.

Notes:
  • You can use a command chair in place of the camera. You'll need to man the craft if you do, of course, but a Drood can be used in place of the camera.
  • The MFD panel in the screenshot is "attached" to the end of the horizontal strut, but rotated 180° and displaced to the location shown.
    The fact that the camera is no longer touching the strut doesn't matter -- as far as the game is concerned, the panel is attached to the end of the strut. The connection strength (how hard it is to break the connection between the MFD and the strut) isn't effected in the slightest by the fact that it is "floating" in mid-air.
Adding Vizzy code to your MFD
First you need to edit the properties of the MFD so that it has Vizzy code. Select the MFD, select the properties panel, and set the "MFD Program" to "Custom" as shown.
While you are here, you should update the part name (top line) to a single word. I used "HUD", but the exact name isn't critical.
Then click on the "Edit Flight Program"
Once you've set the MFD Program to Custom, you can select the camera and click on the "X" icon to the right to bring up the Vizzy code for editing. Note that if you fail to select the MFD panel before clicking on the "X", the game will bring up the Vizzy code attached to whatever part is marked as the primary command pod. This may be an empty page (if you haven't attached any Vizzy code to the command pod). Just exit the Vizzy editor and try again.
If you've done it correctly, the Vizzy editor will open and you'll have a new category of Vizzy instructions named "Multi-Function Display".
I am not going to try to go over all of these commands in detail, but some general notes:
  • All objects that you can create using these commands are "Widgets," and are compatible with the "Set Widget" and "Get Widget" commands. Most objects you create will also have a more specific type and you can use these objects with the specialty commands in addition to the widget commands.
    For example, a "Rectangle" object it is a sprite in addition to being a widget. So you can use the "Set Widget", "Get Widget", "Set Sprite", and "Get Sprite" blocks with this object. You can see which type an object is at the top of the screen by using the "Create Widget" block -- when a specific type of object is selected from the dropdown, the tooltip (at the top of the screen) will show what type the object will be. For example, .
  • You can embed objects within other objects by setting the "Parent" property using the correct Vizzy block () after you create the widget. Doing this allows you to create a set of objects a single time, then use the Set Widget Size / Position / Scale / Rotation / Color blocks to manipulate the whole collection of objects with a single Vizzy operation. This is much faster than manipulating the objects individually each frame.
  • You'll almost certainly want to use an online color picker (Google search[www.google.com]) to select colors. The output of any color wheel that is intended to be used with HTML is correctly formatted to be passed to the "Hex Color" block.
Creating a hollow box in your MFD
You might think this is an easy task -- just create a rectangle and that's all there is to it.

Well...

The problem is this creates a filled rectangle. You can set the color of the fill using the "Set Widget Color", but the whole rectangle will be filled in with a color. This isn't an issue when you are using a MFD as, well, a MFD, but when you are using it as a HUD, a filled in rectangle will always be opaque (there is no "transparent" color). Furthermore, despite the fact that there are several options that can be selected via "set sprite "Fill Method"", none of them produce a hollow rectangle. There are two ways that I'm aware of to create a hollow rectangle:
  1. Create a Rectangle that isn't filled in at all (via ), then create four lines to mark the border, setting each line's parent to the Rectangle sprite. This works, but keep in mind that if you plan to scale the rectangle down to a reasonable size then you'll need to make the lines more than one pixel thick. Otherwise, the lines will disappear when you scale the object down.
  2. Set the icon for the rectangle to "UI/Sprites/Border/TooltipBorder-1px".
The second method is much, much easier to implement, but its up to you.
This code creates a hollow box at the center of the screen. You can move it around using this code:
Some notes:
  • The coordinate system used by an MFD has 0,0 at the center of the screen, rather than the upper left hand corner.
  • The units of the coordinate system uses is "1 / 256th of a meter". Yes, this means that fractional co-ordinates are valid, so (0.1283, -4.3873) is a valid position for an object. It also means that the number of "pixels" available can be calculated by taking multiplying the width and height of the screen by 256 and dividing by 2. This requires using a FUNK expression, as shown in the code above.
  • The position of a widget is, by default, is the center of the widget. This can be changed, if desired, but in most cases the default is preferred.
Displaying the box so that it marks a PCI location
The rest of this guide assumes that you arrived here from the section X this guide:
https://steamproxy.com/sharedfiles/filedetails/?id=2944674093
Reading that guide isn't strictly required to continue to follow the example -- but you'll likely be scratching your head wondering "Why are we going through all this trouble."
First, we need to create an event on the MFD to receive messages from the primary "command pod", then call a custom instruction to move the box. This is easy enough:
Next, we need to add a new thread in the Vizzy code attached to the command pod that continuously sends the position of interest to the MFD. This is also easy:
If you aren't following along with my other guide, this code should suffice to allow you to continue to follow along:
Next, we create a custom expression and a custom instruction.
Comments
  • The position returned by isn't what we are looking for. First, it is pointing at the center of the surface of the MFD, but the actual display (which is what we are interested in) is embedded within the MFD. The "-0.025" Z compensates for this. The "-0.0647043" compensates for something (if you don't include it, the box won't be drawn at the right position) but I'm honestly uncertain exactly what.
  • I created a custom expression for calculating the true MFD position because there is another stored instruction (covered later) that also uses this term.
  • How, exactly, "ProjectionScale" works I don't have a clue. But it does correctly produce the point at which the vector from the target to the camera intersects with the display surface of the MFD. If you really want to know, ask @GoldenShoe on the JSC Discord server[discord.gg], but I don't understand it.
  • This will happily draw the box off the screen, so you'll want to make sure the HUD is pointed in the right direction if you actually want to see the box.
  • It will also draw a box at the position where the vector from the target to the camera passes through the MFD after first passing through the camera. In other words, if the MFD panel is pointed 180° away from the target, it will produce a box in the center of the screen. How to check for this condition is covered later in this guide.
At this point, the code should create a box if the target location is within the field of view of the camera and lies beyond the MFD. You can verify the calibration by checking with a nearby target -- the MFD created reticle should align with the game created reticle exactly, and remain aligned as you maneuver the vehicle. If it doesn't, try adjusting the hard coded values in "Get True MFD PCI Position."
Creating gimbals for the camera / MFD
If you are only interested in creating a HUD for a plane, rover, or boat that you plan on controlling manually, this section is unlikely to be of interest to you.

However, if you are creating a rocket, it would be beneficial if the camera always pointed at the target (regardless of the orientation of the rocket). This is... Mostly doable. :)

First, we need to make our simple camera / MFD much more complex:
Notes:
  • Two motors have been added -- the bottom motor is to control yaw (aka "Heading") while the second motor is to control pitch. Both motors should be sized down as much as possible and the break torque and static resistance set to the maximum value. Additionally:
    1. The lower motor (the one pointing up) should be named "Camera Yaw Motor", and have its "Motor Input" set to "HUD.VZ.Camera_Yaw_Motor_Input."
      This, and the following step, assumes that you renamed the "Multi-Function Display" to "HUD". If you named it something else, then substitute the proper name.
      This can be done by clicking on the text that sits between the two arrows under "Motor Input" / "Input" and typing in the desired value. Like this:
      If you prefer, you can use sliders to set this value (it might be easier for troubleshooting), but the guide assumes that you have set it to a variable as shown.
    2. The upper motor (the one pointing at the camera) should be named "Camera Pitch Motor", its torque should be turned up all the way, and its "Motor Input" should be set to "HUD.VZ.Camera_Pitch_Motor_Input".

    You will need to displace and rotate the upper ("Pitch") motor to position it properly. Ideally, it should be positioned so that the centerline of the vehicle passes through the center of this motor.
  • Note that the strut that attaches to the pitch motor has been rotated to be vertical, then displaced to be directly above the strut from the yaw motor.
    As far as I know, this all has to be setup manually -- and, if you get it wrong, you'll find it much, much, much harder to control yaw. An enormous amount of time was spent rotating the camera in the designer to see if things lined up properly, then testing in the game was required to get everything aligned well enough for things to work. If things are lined up perfectly, then the yaw should remain constant if the motor is turned off without maxing out the "braking force" and "static resistance" parameters of the pitch motor. Good luck with that. :)
  • The strut that extends to the left, and the MFD that hangs below this strut needs to exactly mirror the strut to the right and the MFD above. Edit the properties of this MFD to:
    1. Make sure the name isn't the same as the name of your HUD MFD.
    2. Under "Additional Settings", set "Autoactivation" to off. This prevents the MFD from drawing power.
    The sole purpose of this MFD and strut is to serve as a counterweight to the active MFD and strut. This also has to be positioned exactly correctly or you'll find it all but impossible to control the pitch.

    You can't use radial symmetry here, because you want the MFD to be below the strut, and all the symmetry options end up positioning it above the strut.

    If you are playing in sandbox, you can do away with this counterweight by using the tinker panel to set the mass of the lateral strut and MFD to zero. This isn't an option in career mode, however -- thus, the elaborate setup.

    Obviously, you don't need to use a MFD here -- just adding deadweight to the end of the strut will achieve the same goal. Another strut would serve just as well (using the dead-weight slider to get the mass correct). However, this would be even more impossible to balance properly, so I just went with the MFD.
You'll also want to put this whole thing in a nose cone, like this:
As the camera is never supposed to be inside a nose cone, the interior surfaces of the nose cone don't exist. Thus, the nose cone doesn't exist from the inside, and the camera will still be able to see the game world. A fairing, on the other hand, does include interior surfaces, so this trick won't work with a fairing.
The nose cone is required to ensure that the MFD panels don't experience drag forces -- otherwise, there will be a number of different drag forces acting on the MFD and struts trying to push them out of position. In theory, a complex enough PID could compensate for these forces (at low velocities), but... The nose cone eliminates this variable altogether.
You may want to omit the nose cone initially. While it is necessary for a robust solution, the need to remove the nose cone to edit the Vizzy code on the MFD panel is... Tedious, to say the least.
We need to grab the part IDs of the two motors in the init "OnStart" event:
And we need to update the update position event to call a new custom instruction:
And, finally, we need to actually command the motors to move the camera to point at the target.
Comments:
  • To avoid unnecessary complexity, this code uses the current position of the box to calculate the error in pointing.
  • The if statement checks to see if the camera is closer to the target than the MFD is -- if it is, we are pointing the wrong direction, and need to rotate the camera at a fixed rate until the MFD is closer. While it is in this mode, errors in yaw are ignored.
  • The MFD panel is closer to the target than the camera, we rotate the camera until the X position of the box is zero.
  • For yaw we check to see if the position is off by more than 5 display units (remember, a display unit is 1/256th of a meter, so this is checking to see if the box's current position is more than ~0.02 meters from the center point of the MFD).
  • If it is, we turn on the yaw motor at 30% power until the yaw error is less than 5 display units.
This is very janky, and its very sensative to tuning the various constants. Some guidance on tuning:
  • If an unwanted roll occurs, increasing the rate at which heading errors are reduced to zero (changing the "-50" to, say, "-10") will improve matters -- at the risk of causing "bouncing" in the yaw plane. This is caused when the motor setting is large enough to cause the aim point to move past the targeted value before the Vizzy code is executed again.
  • If there is bounce in the pitch plane, then increasing the "5" in the if statement will help -- at the cost of increasing the abruptness of pitch changes.
  • There is a minimum value for the yaw motor to move the pitch properly at all, and with my setup it is ~30%. Below this value, the off balance struts / MFDs will produce greater torque than the motor is outputting, and once the "breaking force" and "static resistance" forces are removed (which happens automatically when the motor is set to a non-zero power setting) the pitch value will move to one of the two extreme values (pointing straight up or pointing straight down) and be stuck there. If your setup is getting stuck pointing straight up or down, increase this value. You'll also need to increase the "5" in the if statement, because otherwise the yaw will start bouncing.
Conclusion
That's it, folks. The gimbaling controller is hardly ideal, but it suffices for the purpose that I want to use it for, so... Yeah, I'm not going to fiddle with it any more.

There are, of course, many, many things that could be usefully added to the HUD. Some options off of the top of my head:
  • A surface velocity vector. I may end up adding this to the guide.
  • Distance to the target. I may end up adding this to the guide.
  • On screen controls to trigger changing the target in response to mouse clicks. I may end up adding this to the guide (but probably not).
    Note that having onscreen arrow buttons that the user can click on is much, much, much easier than allowing the user to click anywhere and moving the target to the terrain that is underneath the mouse. The first only requires a relatively simple calculation to displace the target in the requested direction -- the later requires a very expensive raycast from the camera to determine the position where the vector intersects terrain.
  • "Paper tapes" that show altitude and speed.
  • A pitch ladder that shows the current pitch values.
  • A current heading indicator.
  • Performance information on the current stage (commanded throttle vs. actual throttle, command altitude vs actual altitude are two obvious candidates).
  • Allow for multiple "boxes" to exist at the same time, each tracking different targets.

However, since the purpose of this guide is mostly to support my other guide, I'm not going to implement any of the above. If you do, feel free to include a link to your craft file in the comments of this guide,as I'm sure others would be interested.

Imporant
It turns out that you can add a reticule to the game world via the "Target Node" block. All you need to do is set the target node to a PCI position vector, like this:
It sure would be nice if this were documented somewhere (else)...

There are some (very minor) drawbacks associated with this:
  • This code will need to be re-run each physics tick. The PCI axis as inertial, so unless you have a target that isn't moving relative to non-rotating center of the planet the PCI coordinates of the target will change with each physics tick. The HUD solution only need to be updated each tick when the target moves relative to the craft.
  • Due to the previous point, the location of the target reticule is subject to jitter. Assuming you update it every physics tick, this is minor except at close (< 50 meters) range. If you can't update it every frame due to executing other Vizzy code, then the jitter will be worse. The reticule in the HUD doesn't have this problem as long as the relative velocity is low.
In any case, this new functionality pretty much obsoletes the HUD that was built above -- the main purpose was to simulate adding a reticule, after all.

I'm going to leave this guide posted because its still useful to have explicit instructions about how to make a HUD transparent and someone might be able to repurpose the Vizzy code for other purposes.
3 Comments
grzyb11 2 Aug, 2024 @ 2:06pm 
transparent mfd doesnt work anymore apparently
mreed2  [author] 6 May, 2024 @ 12:16pm 
I decline for two reasons:

1) It does not work well, even in the very limited context that I attempted to use it. The trials and tribulations of trying to get it to work are detailed in the guide.
2) There is enough information in the guide to construct the HUD for yourself. If you are unable or unwilling to follow the instructions, then you won't be able to troubleshoot the issues when it doesn't work -- see the prior point.
3) I don't have a craft file that contains only the HUD, and I'm not going to spend the time to filter out the unnecessary code to produce a craft file that won't work and anyone who needs it won't understand.
volcano.mitchell 6 May, 2024 @ 11:40am 
Please give us the XML save for this