Jump to content

World Space and Wireframes


Recommended Posts

I have two questions related to a plugin I'm working on.

The first, drawing lines and shapes in world space. I've played around with examples from the Unity Documentation, but have yet to get them to work in KSP. I'm particularly interested in GL. Any advice or just a point to a simple working example would be very helpful.

Second. I want to take an existing 3D gameobject (a ship for sample) and render a wireframe 2D representation of that ship. I'm sure this will be no easy feat, but I've seen reference to gameobject.renderer.wireframe in documentation, but again find that I am unable to get that example working within KSP. Any advice on this or perhaps a different way to draw a 2D representation of a 3D game object in a window?

Thanks in advance,

NOS

Link to comment
Share on other sites

Ugh, lines.

I never figured that out myself either. I spent a couple hours on it trying to figure out how the pitch reference worked before I gave up on it.

Note that one type of lines need debug enabled to render, which the release .exe of KSP has disabled. The Unity docs mention which one it is.

On the wireframe render, I'm thinking you will have to render the Parts in wireframe, not the Ship. Remember that there is no Ship wireframe to reference, it is a grouping of Parts as far as the game engine is concerned.

Also, are you making a seperate camera for the wireframe? If you are trying to wireframe in the main camera window, would you even see the wireframe past the textures that are still being rendered?

Disclaimer: Have not tried anything remotely like this myself, but hopefully my $0.02 is worth at least $0.01

D.

Link to comment
Share on other sites

I've seen many people draw lines in their plugins. MechJeb for landing markers, everything LazerTech, Hydrotech guide lines, so forth and etc. But, as I'm new to C# and Unity, I can't as of yet follow their complex code to make use of it. So I know the lines is possible, I just need a simple example. There was a simplish mod, Lightswitch, but it was abandoned and all the source taken down before I could grab it.

Essentially, I want to write a IVA HUD plugin and/or in Flight "radar" system.

Yes, the I guessed the wireframe rendering would have to be done per part and the idea is drawing it in a window minus the texture. I found a JScript example which looks promising, but having a big of pain converting it to C#. Particularly due to the .toBuiltIn() function that is present in JScript but not in C#.

Link to comment
Share on other sites

This is the script to test the wireframe I've adapted from here. Not sure if my conversion from JScript to C# is accurate.

namespace kADAR
{
[KSPAddon(KSPAddon.Startup.Flight, false)]
public class kADAR : PartModule
{
public FlightGlobals vessel = null;
Color lineColor = Color.red;
Color backgroundColor = Color.black;
public bool Zwrite = true;
public bool Awrite = true;
public bool blend = true;
private Vector3[] lines = null;
ArrayList linesArray = null;
private Material lineMaterial;
private MeshRenderer meshRenderer;
int i;

public void Start()
{
var vessel = this.vessel;
//var Zwrite = true;
//var Awrite = true;
//var blend = true;
Debug.Log("Start()");
meshRenderer = GetComponent("MeshRenderer") as MeshRenderer;
if (!meshRenderer) meshRenderer = gameObject.AddComponent("MeshRenderer") as MeshRenderer;
meshRenderer.material = new Material("Shader \"Lines/Background\" { Properties { _Color (\"Main Color\", Color) = (1,1,1,1) } SubShader { Pass {" + (Zwrite ? " ZWrite on " : " ZWrite off ") + (blend ? " Blend SrcAlpha OneMinusSrcAlpha" : " ") + (Awrite ? " Colormask RGBA " : " ") + "Lighting Off Offset 1, 1 Color[_Color] }}}");
lineMaterial = new Material("Shader \"Lines/Colored Blended\" { SubShader { Pass { Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Front Fog { Mode Off } } } }");
lineMaterial.hideFlags = HideFlags.HideAndDontSave;
lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave;
var linesArray = new ArrayList();

var filter = GetComponent("MeshFilter") as MeshFilter;
var mesh = filter.mesh;
var vertices = mesh.vertices;
var triangles = mesh.triangles;

for (i = 0; i < triangles.Length / 3; i++)
{
linesArray.Add(vertices[triangles[i * 3]]);
linesArray.Add(vertices[triangles[i * 3 + 1]]);
linesArray.Add(vertices[triangles[i * 3 + 2]]);
}

//Vector3 Varray = new Vector3(linesArray.Count);
linesArray.CopyTo(lines);
}
public void OnRenderObject()
{
Debug.Log("OnRenderObject()");
meshRenderer.material.color = backgroundColor;
lineMaterial.SetPass(0);
//GL.
GL.PushMatrix();
GL.MultMatrix(transform.localToWorldMatrix);
GL.Begin(GL.LINES);
GL.Color(lineColor);
for (i = 0; i < lines.Length / 3; i++)
{
GL.Vertex(lines[i * 3]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i *3]);
}
GL.End();
GL.PopMatrix();
}
}
}

But I get this in the error log:

Matrix stack full depth reached

(Filename: Line: 32)

NullReferenceException: Object reference not set to an instance of an object
at kADAR.kADAR.OnRenderObject () [0x00000] in <filename unknown>:0

How do I set the object reference with GL?

Edited by noonespecial
Link to comment
Share on other sites

You should debug your code. The "easiest" way in ksp is to add a bunch off debug statements. Add sth like

UnityEngine.Debug.Log("MyText");

between every line and look whats the last log before you get an exception. Also make sure to search for the very first error and solve it. If the initialization does not work, your Update-Stuff will most likely fail as well (spamming your ksp.log). Sadly Squad does not offer us any more advanced way of debugging i am aware of :(

Matrix Stack full is most likely the case since you have an exception after Pushing a matrix, so it's not pop'ed again. NullReferenceException means that you try to call or use an Property that is null. For example meshRenderer. Have you made sure its not null? Same for meshRenderer.material and pretty much everything else...

Link to comment
Share on other sites

You can get extra details on the error by using a try/catch statement. You can test values for null before you use them to prevent NullReferenceExceptions. Here's your OnRenderObject method modified as an example of tracing down a null object and catching error messages:


public void OnRenderObject()
{
try
{
Debug.Log("OnRenderObject()");

if (meshRenderer == null)
Debug.Log("OnRenderObject: meshRenderer was null.");
else if (meshRenderer.material == null)
Debug.Log("OnRenderObject: meshRenderer.material was null.");
else
meshRenderer.material.color = backgroundColor;

if (lineMaterial == null)
Debug.Log("OnRenderObject: lineMaterial was null.");
else
lineMaterial.SetPass(0);

//GL.
GL.PushMatrix();
if (transform == null)
Debug.Log("OnRenderObject: transform was null.");
else
GL.MultMatrix(transform.localToWorldMatrix);
GL.Begin(GL.LINES);
GL.Color(lineColor);
for (i = 0; i < lines.Length / 3; i++)
{
GL.Vertex(lines[i * 3]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i * 3]);
}
GL.End();
GL.PopMatrix();
}
catch (Exception ex) // Catch all exception types.
{
Debug.Log("OnRenderObject error: " + ex.Message);
}
}

Link to comment
Share on other sites

Thanks Pizzaoverhead! That piece of information will be very useful in most, if not all, future endeavors!

So, using that pieces of information, I was able to find and eliminate three obvious problems. Firstly, two of the materials weren't loading. After some tinkering, I changed the meshRenderer var to _meshRenderer = gameObject.GetComponent<MeshRenderer>();. I found a similar error with the shaders. Apparently the fixes from the JScript conversion wasn't precise, as I thought. Also noticed that some of the vars weren't visibile to the OnRenderObject() function, so I copied the declarations to here (still need to figure this one out).

However, even with those, it was still returning an error about no game object. So I changed Start() to Start(Vessel myVessel) and OnRenderObject() to OnRenderObject(Vessel myVessel).

So now... no errors in the log!

Now the problem is.... there is still absolutely nothing displaying in KSP and with no errors to track, I'm at a loss as to what to do. Any advice?

using System;
using KSP.IO;
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

namespace kADAR
{
[KSPAddon(KSPAddon.Startup.Flight, false)]
public class kADAR : PartModule
{
public FlightGlobals myVessel = null;
Color lineColor = Color.red;
Color backgroundColor = Color.black;
public bool Zwrite = true;
public bool Awrite = true;
public bool blend = true;
private Vector3[] lines = null;
ArrayList linesArray = null;
private Material lineMaterial;
public MeshRenderer _meshRenderer;
int i;
public string _shader = "Lines/Background";

public void Start(Vessel myVessel)
{
myVessel = this.vessel;
//var Zwrite = true;
//var Awrite = true;
//var blend = true;
Debug.Log("Start()");
//gameObject.AddComponent(typeof(MeshRenderer));
//_meshRenderer = (MeshRenderer)GetComponent(typeof(MeshRenderer));
_meshRenderer = gameObject.GetComponent<MeshRenderer>();

if (!_meshRenderer)
//_meshRenderer = (MeshRenderer)gameObject.AddComponent(typeof(MeshRenderer));
_meshRenderer = gameObject.AddComponent<MeshRenderer>();

//_meshRenderer.material = new Material("Shader \"Lines/Background\" { Properties { _Color (\"Main Color\", Color) = (1,1,1,1) } SubShader { Pass {" + (Zwrite ? " ZWrite on " : " ZWrite off ") + (blend ? " Blend SrcAlpha OneMinusSrcAlpha" : " ") + (Awrite ? " Colormask RGBA " : " ") + "Lighting Off Offset 1, 1 Color[_Color] }}}");
_meshRenderer.material = new Material(Shader.Find("Particles/Additive"));
//lineMaterial = new Material("Shader \"Lines/Colored Blended\" { SubShader { Pass { Blend SrcAlpha OneMinusSrcAlpha ZWrite Off Cull Front Fog { Mode Off } } } }");
lineMaterial = new Material(Shader.Find("Particles/Additive"));
lineMaterial.hideFlags = HideFlags.HideAndDontSave;
lineMaterial.shader.hideFlags = HideFlags.HideAndDontSave;
var linesArray = new ArrayList();

//var filter = GetComponent(typeof( MeshFilter));
var filter = GetComponent<MeshFilter>();
var mesh = filter.mesh;
var vertices = mesh.vertices;
var triangles = mesh.triangles;

for (i = 0; i < triangles.Length / 3; i++)
{
linesArray.Add(vertices[triangles[i * 3]]);
linesArray.Add(vertices[triangles[i * 3 + 1]]);
linesArray.Add(vertices[triangles[i * 3 + 2]]);
}

//Vector3 Varray = new Vector3(linesArray.Count);
linesArray.CopyTo(lines);
}

public void OnRenderObject(Vessel myVessel)
{
if (!myVessel)
{
Debug.Log("OnRenderObject: myVessel is null " + myVessel);
myVessel = this.vessel;
}

if (!myVessel)
Debug.Log("OnRenderObject: myVessel is STILL null " + myVessel);
try
{

_meshRenderer = GetComponent<MeshRenderer>();
if (!_meshRenderer)
_meshRenderer = gameObject.GetComponent<MeshRenderer>();
if (!_meshRenderer)
_meshRenderer = gameObject.AddComponent<MeshRenderer>();
_meshRenderer.enabled = true;
lineMaterial = new Material(Shader.Find("Particles/Additive"));
Debug.Log("OnRenderObject()");
if (_meshRenderer == null)
Debug.Log("OnRenderObject: meshRenderer is null");
else if (_meshRenderer.material == null)
Debug.Log("OnRenderObject: meshRenderer.material is null");
else
_meshRenderer.material.color = backgroundColor;

if (lineMaterial == null)
Debug.Log("OnRenderObject: lineMaterial is null");
else
lineMaterial.SetPass(0);

GL.PushMatrix();
if (transform == null)
Debug.Log("OnRenderObject: transform is null");
else
GL.MultMatrix(transform.localToWorldMatrix);

GL.Begin(GL.LINES);
GL.Color(lineColor);
for (i = 0; i < lines.Length / 3; i++)
{
GL.Vertex(lines[i * 3]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 1]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i * 3 + 2]);
GL.Vertex(lines[i * 3]);
}
GL.End();
GL.PopMatrix();

}
catch (Exception ex)
{
Debug.Log("OnRenderObject error: " + ex.Message);
}
}

}
}

Link to comment
Share on other sites

The KSPAddon code uses an awkward way of calling plugins that searches for methods that exactly match "Start()" and "OnRenderObject()". You can't add any parameters to them because KSP won't know what values you want them to be given. With your current code, as far KSP is concerned, it looked for a Start() and an OnRenderObject() method, found neither, so it's job was done.

Instead of creating the vessel as a parameter, retrieve it using FlightGlobals.ActiveVessel.

So in this case:


public void Start(Vessel myVessel)
{
myVessel = this.vessel;

becomes:


public void Start()
{
Vessel myVessel = FlightGlobals.ActiveVessel;

and the same for OnRenderObject().

Edited by pizzaoverhead
Link to comment
Share on other sites

You mean you still get an NullReferenceException? That means there still IS an error, you just failed to track it. Well, maybe you do in fact get nothing, but thats... another issue.

1. Where did you get those

public void OnRenderObject(Vessel myVessel)

from? Its a method called from the unity engine, so you have to follow the unity specs. They do not mention any arguments, so you better remove that "Vessel myVessel". Same for Start(...), ofc.

2. Does your code even compile? I would be pretty surprised, since !object shouldn't be possible. Its definitively not a null-check. It has to be "myVessel == null".

3. If unity would accept your malformed OnRenderObject function, it would still not give you a vessel (see 1). In that case even after fixing 2 you would get an un-catched NullReferenceException. In the Debug.Log(...) you are adding strings. A Vessel isn't a string, so C# tries to call myVessel.toString(). But myVessel is null, that means no method can be called and it throws the mentioned exception.

Link to comment
Share on other sites

The KSPAddon code uses an awkward way of calling plugins that searches for methods that exactly match "Start()" and "OnRenderObject()". You can't add any parameters to them because KSP won't know what values you want them to be given. With your current code, as far KSP is concerned, it looked for a Start() and an OnRenderObject() method, found neither, so it's job was done.

Instead of creating the vessel as a parameter, retrieve it using FlightGlobals.ActiveVessel.

So in this case:


public void Start(Vessel myVessel)
{
myVessel = this.vessel;

becomes:


public void Start()
{
Vessel myVessel = FlightGlobals.ActiveVessel;

and the same for OnRenderObject().

I changed those to your suggestions and it went from no error back to:

Start()

(Filename: C:/BuildAgent/work/7535de4ca26c26ac/Runtime/ExportGenerated/StandalonePlayer/UnityEngineDebug.cpp Line: 54)

NullReferenceException: Object reference not set to an instance of an object
at kADAR.kADAR.Start () [0x00000] in <filename unknown>:0

and

OnRenderObject()

(Filename: C:/BuildAgent/work/7535de4ca26c26ac/Runtime/ExportGenerated/StandalonePlayer/UnityEngineDebug.cpp Line: 54)

OnRenderObject error: Object reference not set to an instance of an object

The debug.logs didn't catch anything else.

You mean you still get an NullReferenceException? That means there still IS an error, you just failed to track it. Well, maybe you do in fact get nothing, but thats... another issue.

I just noticed the NullReferenceExceptions. Appears to be three.

NullReferenceException
at (wrapper managed-to-native) UnityEngine.Component:InternalGetTransform ()

at UnityEngine.Component.get_transform () [0x00000] in <filename unknown>:0

at PQSCity.OnSphereReset () [0x00000] in <filename unknown>:0

at (wrapper delegate-invoke) PQS/OnDefaultDelegate:invoke_void__this__ ()

at PQS.ResetSphere () [0x00000] in <filename unknown>:0

at PQS.OnDestroy () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

NullReferenceException
at (wrapper managed-to-native) UnityEngine.MonoBehaviour:StopCoroutine (string)

at PQS.ResetSphere () [0x00000] in <filename unknown>:0

at PQS.ResetSphere () [0x00000] in <filename unknown>:0

at PQS.OnDestroy () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

NullReferenceException
at (wrapper managed-to-native) UnityEngine.Component:InternalGetGameObject ()

at UnityEngine.Component.get_gameObject () [0x00000] in <filename unknown>:0

at MapView.OnDestroy () [0x00000] in <filename unknown>:0

(Filename: Line: -1)

1. Where did you get those
public void OnRenderObject(Vessel myVessel)

from? Its a method called from the unity engine, so you have to follow the unity specs. They do not mention any arguments, so you better remove that "Vessel myVessel". Same for Start(...), ofc.

I got the idea from a random Unity examples while searching the error. I removed it and the errors returned.

2. Does your code even compile? I would be pretty surprised, since !object shouldn't be possible. Its definitively not a null-check. It has to be "myVessel == null".

Yes, it compiled without error or warning. No change. The if statements still do not generate a debug.log.

3. If unity would accept your malformed OnRenderObject function, it would still not give you a vessel (see 1). In that case even after fixing 2 you would get an un-catched NullReferenceException. In the Debug.Log(...) you are adding strings. A Vessel isn't a string, so C# tries to call myVessel.toString(). But myVessel is null, that means no method can be called and it throws the mentioned exception.

I also removed those from the Debug.Log

Debug.Log("OnRenderObject: myVessel is NULL")

The if statement with (myVessel == null) or (!myVessel) are not called so I can only assume that myVessel is not NULL. So I did a debug log to see what it was returning and got:

myVessel: Mark1-2Pod (Untitled Space Craft) (Vessel)

Edited by noonespecial
Link to comment
Share on other sites

Yes, it compiled without error or warning. No change. The if statements still do not generate a debug.log.

Oh, you are right, Unity does in fact perform a != null check for you when you convert an object into a bool.

Anyway, you get an error and have no clue where it comes from? You have to Debug. The only real way to trace what your KSP-Mod actually does is by adding a bunch of Logging, for example via Debug.Log. Try to copy-paste them between every lines (watch out for single line statements without braces) that don't have to log anything but an unique identifier (a, b, c, ...). Ofc you should remove that stuff again, once your code has proven to work as intended.

Link to comment
Share on other sites

This thread is quite old. Please consider starting a new thread rather than reviving this one.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...