[Solved] Complex 3D nodes in lemur not rendering correctly

I’m having trouble testing out the 3D rendering of nodes with geometry children since it appears to make materials transparent/semi-transparent in some places or just doesn’t render some child geometries. Looks like there isn’t a Z buffer or something maybe?

Marked some of the see through glitches with red arrows and the cyan arrows show that the turbine blades (which are linked to a bone) get displaced/vanish completely.

How it should look like for reference (nifty + separate viewport):

Lemur init:

	GuiGlobals.initialize(CPU.game);
	BaseStyles.loadGlassStyle();
	GuiGlobals.getInstance().getStyles().setDefaultStyle("glass");
	
	Styles styles = GuiGlobals.getInstance().getStyles();
	Attributes attrs;
	attrs = styles.getSelector("glass");
	attrs.set("fontSize", 14);

	QuadBackgroundComponent bg = new QuadBackgroundComponent(new ColorRGBA(0, 0, 0, 0.5f));
	attrs = styles.getSelector("button", "glass");
	attrs.set("color", new ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f));
	attrs.set("background", bg);

	BitmapFont font = CPU.game.getAssetManager().loadFont("assets/font/neuropolitical.fnt");
	attrs = styles.getSelector("slider", "button", "glass");
	attrs.set("fontSize", 14);
	attrs.set("font", font);

Code:

//import 60 lines of imports

@SuppressWarnings({"rawtypes" })
public class TradeScreen extends Container{

private Terminal t;
private int i;
private ArrayList<Class> moduletypes = new ArrayList<Class>();
private Mod spin;
private float angle = 0;
private Quaternion startrotat;
private Label name, desc;

@SuppressWarnings("unchecked")
public TradeScreen(Terminal t) {
	super();
	
	setLocalScale(GUI.hei/1080);
	
	moduletypes.add(Block.class);
	moduletypes.add(Block.class);
	moduletypes.add(Block.class);
	moduletypes.add(Block2.class);
	moduletypes.add(Block2.class);
	moduletypes.add(Block2.class);
	moduletypes.add(Block4.class);
	moduletypes.add(Block4.class);
	moduletypes.add(Block4.class);
	moduletypes.add(Block_Slope.class);
	moduletypes.add(Block_Slope.class);
	moduletypes.add(Block_Slope.class);
	moduletypes.add(Pylon.class);
	moduletypes.add(LongCommand.class);
	moduletypes.add(LongCommand.class);
	moduletypes.add(LongCommand.class);
	moduletypes.add(BasicEngine.class);
	moduletypes.add(BasicEngine.class);
	moduletypes.add(BasicEngine.class);
	moduletypes.add(Minigun.class);
	moduletypes.add(SimpleCannon.class);
	moduletypes.add(SimpleLaser.class);
	moduletypes.add(SimplePlasma.class);
	moduletypes.add(Reactor.class);
	moduletypes.add(ReactorLong.class);
	moduletypes.add(ReactorOld.class);
	moduletypes.add(Scanner.class);
	moduletypes.add(ScannerFlat.class);
	moduletypes.add(ShieldEmitter.class);
	moduletypes.add(TractorBeam.class);
	moduletypes.add(Cargo.class);
	moduletypes.add(CollectorVent.class);
	moduletypes.add(BussardCollector.class);
	moduletypes.add(CollectorJet.class);
	moduletypes.add(MicroTank.class);
	moduletypes.add(MediumTank.class);
	moduletypes.add(FuelTankMk1.class);
	moduletypes.add(IndustrialTank.class);

	// Add some elements
	Label title = new Label("Trade Terminal");
	title.setFontSize(25);
	title.setColor(ColorRGBA.White);
	title.setFont(CPU.game.getAssetManager().loadFont("assets/font/neuropolitical.fnt"));
	addChild(title);
	
	Container subcont = new Container();
	subcont.setLayout(new SpringGridLayout(Axis.X, Axis.Y));
	addChild(subcont);
	
	name = new Label("name");
	name.setColor(ColorRGBA.White);
	name.setFont(CPU.game.getAssetManager().loadFont("assets/font/neuropolitical.fnt"));
	name.setFontSize(18);
	addChild(name);
	
	desc = new Label("desc");
	desc.setColor(ColorRGBA.White);
	desc.setFont(CPU.game.getAssetManager().loadFont("assets/font/neuropolitical.fnt"));
	desc.setFontSize(15);
	addChild(desc);
	
	startrotat = new Quaternion().fromAngleAxis(30*FastMath.DEG_TO_RAD, Vector3f.UNIT_Y);
	startrotat.multLocal(new Quaternion().fromAngleAxis(30*FastMath.DEG_TO_RAD, Vector3f.UNIT_X));
	
	for (int j = 0; j < moduletypes.size(); j++) {
		Class c = moduletypes.get(j);
		
		Class[] cArg = new Class[4];
		cArg[0] = float.class;
		cArg[1] = float.class;
		cArg[2] = float.class;
		cArg[3] = Faction.class;
		
		Mod b = null;
		try {
			b = (Mod)c.getDeclaredConstructor(cArg).newInstance(50,-50,10,nextFac());
		} catch (Exception e) {
			e.printStackTrace();
			System.exit(1);
		}
		b.setLocalRotation(startrotat);//Const.ZX45.mult(new Quaternion().fromAngleAxis(FastMath.PI, Vector3f.UNIT_Z))
		b.setLocalScale(20);

		Button button = subcont.addChild(new Button(""),j%8,(int)j/8);
		button.setUserData("mod", b);
		button.setPreferredSize(new Vector3f(100,100,0));
		button.setColor(ColorRGBA.White);
		button.setFont(CPU.game.getAssetManager().loadFont("assets/font/neuropolitical.fnt"));
		button.setFontSize(20);
		button.attachChild(b);
		
		final long price = b.getPrice();
		final Faction fac = b.getFaction();
		button.addClickCommands(new Command<Button>() {
			@Override
			public void execute( Button source ) {
				t.buyModule(c, price, fac);
			}
		});
		
		MouseEventControl.addListenersToSpatial(button, new DefaultMouseListener() {			                    
			 @Override
			 public void mouseEntered( MouseMotionEvent event, Spatial target, Spatial capture ) {
				 spin = (Mod)target.getUserData("mod");
				 name.setText(spin.Name());
				 desc.setText(spin.Description());
				 angle = 0;
			 }
			 
			 @Override
			 public void mouseExited( MouseMotionEvent event, Spatial target, Spatial capture ) {
				 if(spin == (Mod)target.getUserData("mod")){
					 name.setText("");
					 desc.setText("");
					 spin = null;
				 }
				 ((Mod)target.getUserData("mod")).setLocalRotation(startrotat);
			 }                        
		 });
	}
	
	AmbientLight a = new AmbientLight();
	a.setColor(ColorRGBA.White.mult(1f));
	addLight(a);
	
	BoundingBox b = (BoundingBox) getWorldBound();
	setLocalTranslation(GUI.wid/2f-350, GUI.hei/2f+(moduletypes.size()-1)/8*50, 0);
}

private Faction nextFac(){
	i = (i+1)%3;
	switch(i-1)
	{
		case 0: return Faction.Agency;
		case 1: return Faction.Industrial;
		default: return Faction.Regular;
	}
}

public void cycle(float tpf)
{	
	if(spin != null){
		spin.setLocalRotation(startrotat.mult(spin.getLocalRotation().fromAngleAxis(angle, Vector3f.UNIT_Y)));
		angle+=tpf*1.5f;
		System.out.println(angle);
	}
	  
}
}

P.S. How is the label wrap mode on labels coming along? Longer descriptions kind of stretch the whole container by default. This looks kind of grim…

The first picture is exactly what I got when I tried attaching those models directly to the gui node.

Wait, isn’t the gui node completely unable to render something like this? In 3.0 it ignored all rotations I think.

Well I added the model to the gui node and added a control that rotates it around Y axis. Looked exactly like thatvstuff above there.

Okay, so uh. Next question: How to make stuff in the gui node look as it should and not half broken :slight_smile:

What you want to do is not use a parallel projection camera, which is what is used to render the guiNode. You want to setup a perspective view camera. Here’s how I did it in Space Rocks using a method available in my GMath library to calculate the position of the camera.

float fov = 49.134f;
float altitude = GMath.fastCamAltitude(fov, height);

uiNode.setQueueBucket(RenderQueue.Bucket.Transparent);
uiCam = new Camera(width, height);
uiCam.setFrustumPerspective(fov, width / (float)height,
        altitude >= 401 ? altitude - 400 : 1, altitude + 400);
uiCam.setLocation(new Vector3f(width / 2f, height / 2f, altitude));
uiCam.setRotation(new Quaternion(0f, 1f, 0f, 0f));
uiView = renderManager.createPostView("3D UI ViewPort", uiCam);
uiView.setBackgroundColor(ColorRGBA.BlackNoAlpha);
uiView.setClearFlags(false, true, true);
uiView.attachScene(uiNode);

where width and height is the screen resolution. For Lemur, assuming you’ve already initialized it, you’ll also want to:

stateManager.getState(BasePickState.class).addCollisionRoot(uiView, BasePickState.PICK_LAYER_GUI);
GuiGlobals.getInstance().setupGuiComparators(uiView);

In doing this you can treat objects in uiNode the same way you’d treat objects in guiNode except that they are rendered in 3D rather than 2D.

You can grab the GMath library here: http://1337atr.weebly.com/gmath.html

It has a number of mathematical functions I find useful.

P.S. GMath.fastCamAltitude essentially just tells you how far away, on the z-axis, the camera needs to be in order to view a given amount of world units at a given field of view, in degrees.

2 Likes

The gui bucket squashes Z and so complicated models will render strangley.

You can use a parallel projection camera on your own… and then not use the Gui bucket and you won’t have this issue.

@Tryder Awesome, thanks! That works after a few tweaks. The altitude is a wrong though, about twice too high as it should be it seems and the far plane is clipping everything a bit, but I can adjust that.

No, actually I really want to use parallel projection since you can’t have a grid look straight otherwise. The reference in the separate viewport was setup as parallel too.

@pspeed

You can set the near and far planes to whatever you like, you don’t have to use the exact same settings I used. If the altitude seems off you might be plugging in the wrong value. Are you sure you’re putting in the screen height and not the screen width? Also you want your objects to be placed at 0, or close to it, on the z-axis.

As said, the issue is NOT with parallel projection. The Gui Bucket (which is what the standard guiNode is in) is not using regular parallel projection. It sets up its own rendering for the bucket and the Z scale is set to 0… so basically there is no depth.

If you need depth then you can setup your own “gui” node that is not in the gui bucket. Create another viewport, setup its camera as parallel projection, use that for your UI instead of the guiNode.

Label wrapping should work if you set it manually on the text. I guess maybe that’s not exposed. The issue is that you will still need to give it enough vertical space (force a preferred size that’s tall) because there are no iterative layouts.

Well using fastCamAltitude with the height gives me larger image than it should be and the opposite for width. Must be somewhere in between like an average or something…

Edit: Yeah doing a (wid+hei)/2 is pretty close.

That’s what I was trying to point out?

Bingo. The bitmaptext does give you the getHeight() value which can be used to set the required height when bound to a box without any rocket science. Guess I’m making my own label then :confused:

It sounded like you were lamenting having to abandon parallel project… I was just pointing out that parallel project is fine and not really the issue with the guiNode.

Sounds like you don’t really want to go that route, but if you had downloaded GMath back when I originally posted about it you might want to download it again as there was an issue with the camAltitude methods that I fixed a few months ago.

Just an update for any future visitors to this thread:

Found out that setting the camera to paralel projection was the reason the height was always off. Aka this line:

uiCam.setParallelProjection(true);

Fixed it by setting the fov to 0.1 (as close to zero as possible I guess). Not sure why that works but it’s good for now.

Neat.

No I actually downloaded it when you mentioned it here. Cool collection btw, could come useful for some things. Though I don’t really know what half of the things it has even are :smile:

Curious: what do you mean by “the height was always off”?

The camera altitude.

Camera distance has no effect in parallel projection because by definition parallel projection means there is no foreshortening with distance.

Glad you were able to get it to work, though.

Edit: and in the end, even with parallel projection there will always be some side-effects… for example, if you still want y up and x right then z will be backwards from normal UIs. (Except that doesn’t make sense to me now but that was my experience… something was backwards in parallel projection for a 2D UI.)

Yeah it’s pretty odd I’d say too, especially that height affects it and the fov does not.

Well, I’ve done parallel projection to do UIs before putting objects in the root node. It works… but as I said there were some other side-effects. You have to scale instead of use Z for distance foreshortening, carefully set your FOV to imitate 0-width, 0-height like with a regular gui bucket.

For my purposes, I also used a perspective projection in the end but I left the default fov because the reason I went back was that I actually wanted the 3D-ness when all was said and done.

Like in this really old video:

(I preface it with ‘really old’ so hopefully someone doesn’t get excited that there was a Mythruna update… because there wasn’t. :slight_smile: )

1 Like

Btw, I sort of got the text bounds working by commenting out two lines inside the TextComponent which remove the bounding box for some reason:

public void calculatePreferredSize( Vector3f size ) {    
    //bitmapText.setBox(null);

    if( maxWidth > 0 ) {
        // Give the text a box that constrains the width
        bitmapText.setBox(new Rectangle(0, 0, maxWidth, 0));
    }

    size.x = bitmapText.getLineWidth();
    size.y = bitmapText.getHeight();

    if( offset != null ) {
        size.x += Math.abs(offset.x);
        size.y += Math.abs(offset.y);
        size.z += Math.abs(offset.z);
    }

    size.x += 0.01f;
    //bitmapText.setBox(textBox);
}

The panel doesn’t retract back because of this, but at least the text finally gets split into rows. It’s 2-3 lines max so not very noticable anyway. Good enough.