Walls Imparing View of Player (Tricky!) (jME3)

Hello!



I’ve been giving this issue a LOT of thought for a few months. It’s perhaps one of the biggest “I have no idea how to do that” situations I’ve been in with game design. I’ll try my best to explain this problem.



So, let’s say you’re making a third person game with a camera fixed around 30-45 degrees above the floor plane, always facing the player’s center. The camera could be fixed or rotated around this center, I don’t think it really matters too terribly much. Now, in an indoors environment, there are common times that the camera could end up above the ceiling height. That’s all good and fine because the level is a closed design with all the normals facing inward, so you can’t see the ceilings. But, what you can see is the walls of the room right next to you! So say your character is walking down a hallway, if the camera is just so, the walls of adjoining rooms would inhibit your view of anything the player is doing.



Advanced isometric games like Diablo handled this by making the wall semi-translucent, which is a great solution that I would perhaps consider in the long run. The first step would be to do something about these walls in the first place. I’d thought of several solutions, but in some way or another they’re always either too clunky, too difficult to implement, or wouldn’t work all the time. The two ideas I’m flipping around my brain atm (There’s been way more than 2 -.- ) are:


  1. Some sort of trigger box between rooms that basically attaches and detaches rooms that could possibly block your view. - Clunky, and doesn’t really cover the issue of possible walls in that room blocking your view.


  2. Is there a way to perhaps make a smart shader that is updated with the player position and camera position every frame that could check for ZX normals that face the camera - and change the transparency of the material if the normal is indeed facing the camera and indeed between the player and camera?



    Any other ideas on how to do this would be greatly appreciated as well!



    Thank you for reading!

    ~FlaH

Yeah, either picking in the cam direction and hiding/culling all obstacle Geometry or creating a shader that does this for you. I guess both ways would be ok. With an advanced shader you could even do this when the whole level is just one geometry, but I guess it would be some work…

Cheers,

Normen

Yeah, after thinking about it a little harder I think the shader route would just be a beast to control, combined with my lack of knowledge on the matter.



Though, I’ve almost got a fully working test case working with rays. It works perfectly using boxes and solidcolor materials, however I hit a (hopefully) small snag. How do I set the material alpha to a loaded model? :wink: I’ve got Oto the golem just chilling in the room as “furniture,” but I realized that I have no idea how to make him go transparent like the walls. For the walls, I just threw them into the transparent bucket and changed the alpha when the player ray intersects a wall. But for Oto… I mean, I can toss him into the transparent bucket, no problem. But how do I modify his transparency/material alpha?



Thanks!

~FlaH

You should be able to assign a Color to his materials m_Diffuse parameter that has an alpha value.

Alrighty! I almost replied asking another question but realized I could just reload the material for Oto and keep track of it. Thus, giving me access to it. I dunno why, but there’s no Spatial.getMaterial() method. And since I had Oto as a spatial I was sorta screwed.



Anyways, thought I’d post a small test I wrote that displays this functionality. It’s not great, probably lacks some polish, but it works as intended. I think once I implement this for real I’d probably use more than one ray, probably 4-8 so that you don’t have to be behind a wall for it to go transparent, but rather just near one. Also, some controllers to fade the walls in and out instead of just popping in and out. And, I’d probably get rid of the hard coding of the golem material for the ray collision… That’s a bit hacky… heh… :wink:



Thanks for the help as always Normen! :smiley:

~FlaH



[java]package com.flah.playground;



import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResults;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.AnalogListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.material.RenderState.BlendMode;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Ray;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.Bucket;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;



public class WallOcclusionTest extends SimpleApplication {



Geometry geom1;

Spatial golem;

Material golemMaterial;

Node roomNode;



public static void main(String[] args) {

WallOcclusionTest app = new WallOcclusionTest();

app.start();

}



@Override

public void simpleInitApp() {

flyCam.setMoveSpeed(35f);



// Create a box to move around

Mesh mesh1 = new Box(0.5f, 0.5f, 0.5f);

geom1 = new Geometry(“Box”, mesh1);

geom1.move(2, 1, -.5f);

Material m1 = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

m1.setColor(“m_Color”, ColorRGBA.Blue);

geom1.setMaterial(m1);

rootNode.attachChild(geom1);



// Load Oto and keep his material handy

golem = assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);

golemMaterial = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);

golemMaterial.setTexture(“m_DiffuseMap”, assetManager.loadTexture(“Models/Oto/Oto.jpg”));

golemMaterial.setColor(“m_Diffuse”, new ColorRGBA(1, 1, 1, 0.5f));

golemMaterial.setBoolean(“m_UseMaterialColors”, true);

golemMaterial.setBoolean(“m_UseAlpha”, true);

golemMaterial.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

golem.setQueueBucket(Bucket.Transparent);

golem.setMaterial(golemMaterial);

golem.scale(0.5f);

golem.setLocalTranslation(-1.0f, 3f, -0.6f);



// Create a “Room”

roomNode = new Node(“A Room”);

rootNode.attachChild(roomNode);



// Floor

Box floor = new Box(5f, 0.5f, 5f);

Geometry floorGeom = new Geometry(“Floor”, floor);

Material floorMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

floorMat.setColor(“m_Color”, ColorRGBA.Red);

floorMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

floorGeom.setQueueBucket(Bucket.Transparent);

floorGeom.setMaterial(floorMat);

roomNode.attachChild(floorGeom);



// Walls

Box eastWallMesh = new Box(0.5f, 2f, 5f);

Geometry eastWall = new Geometry(“Wall 1 - X5.5”, eastWallMesh);

eastWall.move(5.5f, 2.5f, 0);

Material eastWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

eastWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

eastWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

eastWall.setQueueBucket(Bucket.Transparent);

eastWall.setMaterial(eastWallMat);

roomNode.attachChild(eastWall);



Box westWallMesh = new Box(0.5f, 2f, 5f);

Geometry westWall = new Geometry(“Wall 2 - X-5.5”, westWallMesh);

westWall.move(-5.5f, 2.5f, 0);

Material westWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

westWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

westWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

westWall.setQueueBucket(Bucket.Transparent);

westWall.setMaterial(westWallMat);

roomNode.attachChild(westWall);



Box northWallMesh = new Box(5f, 2f, 0.5f);

Geometry northWall = new Geometry(“Wall 3 - Z5.5”, northWallMesh);

northWall.move(0, 2.5f, 5.5f);

Material northWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

northWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

northWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

northWall.setQueueBucket(Bucket.Transparent);

northWall.setMaterial(northWallMat);

roomNode.attachChild(northWall);



Box southWallMesh = new Box(5f, 2f, 0.5f);

Geometry southWall = new Geometry(“Wall 4 - Z-5.5”, southWallMesh);

southWall.move(0, 2.5f, -5.5f);

Material southWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

southWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

southWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

southWall.setQueueBucket(Bucket.Transparent);

southWall.setMaterial(southWallMat);

roomNode.attachChild(southWall);



// We must add a light to make the model visible

DirectionalLight sun = new DirectionalLight();

sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());

golem.addLight(sun);

roomNode.attachChild(golem);



// Create input

inputManager.addMapping(“MoveRight”, new KeyTrigger(KeyInput.KEY_L));

inputManager.addMapping(“MoveLeft”, new KeyTrigger(KeyInput.KEY_J));

inputManager.addMapping(“MoveUp”, new KeyTrigger(KeyInput.KEY_I));

inputManager.addMapping(“MoveDown”, new KeyTrigger(KeyInput.KEY_K));



inputManager.addListener(analogListener, new String[]{

“MoveRight”, “MoveLeft”, “MoveUp”, “MoveDown”

});

}

private AnalogListener analogListener = new AnalogListener() {

public void onAnalog(String name, float value, float tpf) {

if (name.equals(“MoveRight”)) {

geom1.move(2 * tpf, 0, 0);

}

if (name.equals(“MoveLeft”)) {

geom1.move(-2 * tpf, 0, 0);

}

if (name.equals(“MoveUp”)) {

geom1.move(0, 0, 2 * tpf);

}

if (name.equals(“MoveDown”)) {

geom1.move(0, 0, -2 * tpf);

}

}

};



@Override

public void simpleUpdate(float tpf) {

rootNode.updateGeometricState();



// Reset all the walls to their non-transparent state

for (int i = 0; i < roomNode.getChildren().size(); i++) {

if(roomNode.getChild(i) instanceof Geometry) {

Geometry g = (Geometry) roomNode.getChild(i);

ColorRGBA c = (ColorRGBA) g.getMaterial().getParam(“m_Color”).getValue();

c.a = 1.0f;

} else if (roomNode.getChild(i) instanceof Spatial) {

// Spatial s = (Spatial) roomNode.getChild(i);

golemMaterial.setColor(“m_Diffuse”, new ColorRGBA(1, 1, 1, 1));

}

}



// Build a ray that point the opposite of the camera.

// This would obviously work a LOT BETTER if the cam was focused on the box ;p

Ray ray = new Ray(geom1.getWorldTranslation(), cam.getDirection().negate());

CollisionResults results = new CollisionResults();



roomNode.collideWith(ray, results);



for (int i = 0; i < results.size(); i++) {

System.out.println(results.getCollision(i).getGeometry().getName());



if(results.getCollision(i).getGeometry().getMaterial().equals(golemMaterial)) {

golemMaterial.setColor(“m_Diffuse”, new ColorRGBA(1, 1, 1, 0.4f));

}

else {

Geometry g = results.getCollision(i).getGeometry();



ColorRGBA c = (ColorRGBA) g.getMaterial().getParam(“m_Color”).getValue();

c.a = 0.4f;

// g.getMaterial().setColor(“m_Color”, c);

}

}

}

}[/java]

I’ve often wondered about this myself. Some things I’ve observed while playing some games is sometimes the cam feels like it is inside a collision sphere, where if you were to walk through a door way that is lower then the camera, is seems to move underneath it, then back up again. Just an observation, I’ve never attempted anything similar myself though. :slight_smile:

I couldn’t let this go… Had to finish it… :stuck_out_tongue:



One thing I noticed that was sorta funny is that the ray doesn’t really give a crap what it’s hitting. It just shoots off and you really gotta hope it’s coming back with a spatial or a geometry. I was thinking it’d be nice if I could get a bounding box around Oto so that the player box isn’t hiding between his legs (o_o) and he’s still not faded out, since the ray isn’t hitting him. But, the ray definitely hits a bounding volume, but I have no idea how to relate the volume back to Oto. shrugs Small problem really. - More rays will fix it! :evil:



I was really struggling with finding a way to differentiate Oto from the walls, since the material parameter was different. Though, I really doubt in a real game there would be many “box” geometries laying about, but I figured it might be a quick n dirty thing to have. So I basically made it so that any geometries with a solidcolor material were just named with a keyword (Box) to tell them apart from Oto.



Oh, and I decided to 'not be lazy’™ and added a line to focus the cam on the player box. :stuck_out_tongue:



Source code time, have fun!

~FlaH



WallOcclusionTest:

[java]package com.flah.playground.wallOcc;



import com.jme3.app.SimpleApplication;

import com.jme3.collision.CollisionResults;

import com.jme3.input.KeyInput;

import com.jme3.input.controls.AnalogListener;

import com.jme3.input.controls.KeyTrigger;

import com.jme3.light.DirectionalLight;

import com.jme3.material.Material;

import com.jme3.material.RenderState.BlendMode;

import com.jme3.math.ColorRGBA;

import com.jme3.math.Ray;

import com.jme3.math.Vector3f;

import com.jme3.renderer.queue.RenderQueue.Bucket;

import com.jme3.scene.Geometry;

import com.jme3.scene.Mesh;

import com.jme3.scene.Node;

import com.jme3.scene.Spatial;

import com.jme3.scene.shape.Box;



public class WallOcclusionTest extends SimpleApplication {



Geometry geom1;

// Spatial golem;

// Material golemMaterial;

Node roomNode;

// Node complexRoomNode;



public static void main(String[] args) {

WallOcclusionTest app = new WallOcclusionTest();

app.start();

}



@Override

public void simpleInitApp() {

flyCam.setMoveSpeed(35f);



// Create a box to move around

Mesh mesh1 = new Box(0.5f, 0.5f, 0.5f);

geom1 = new Geometry(“Box”, mesh1);

geom1.move(2, 1, -.5f);

Material m1 = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

m1.setColor(“m_Color”, ColorRGBA.Blue);

geom1.setMaterial(m1);

rootNode.attachChild(geom1);



// Create a “Room”

roomNode = new Node(“A Room”);

rootNode.attachChild(roomNode);



// complexRoomNode = new Node(“Spatials Only”);

// rootNode.attachChild(complexRoomNode);



// Load Oto and keep his material handy

Spatial golem = assetManager.loadModel(“Models/Oto/Oto.mesh.xml”);

Material golemMaterial = new Material(assetManager, “Common/MatDefs/Light/Lighting.j3md”);

golemMaterial.setTexture(“m_DiffuseMap”, assetManager.loadTexture(“Models/Oto/Oto.jpg”));

golemMaterial.setColor(“m_Diffuse”, new ColorRGBA(1, 1, 1, 1));

golemMaterial.setBoolean(“m_UseMaterialColors”, true);

golemMaterial.setBoolean(“m_UseAlpha”, true);

golemMaterial.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

golem.setQueueBucket(Bucket.Transparent);

golem.setMaterial(golemMaterial);

golem.scale(0.5f);

golem.setLocalTranslation(-1.0f, 3f, -0.6f);



roomNode.attachChild(golem);



// Floor

Box floor = new Box(5f, 0.5f, 5f);

Geometry floorGeom = new Geometry("(Box) Floor", floor);

Material floorMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

floorMat.setColor(“m_Color”, ColorRGBA.Red);

floorMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

floorGeom.setQueueBucket(Bucket.Transparent);

floorGeom.setMaterial(floorMat);

roomNode.attachChild(floorGeom);



// Walls

Box eastWallMesh = new Box(0.5f, 2f, 5f);

Geometry eastWall = new Geometry("(Box) Wall 1 - X5.5", eastWallMesh);

eastWall.move(5.5f, 2.5f, 0);

Material eastWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

eastWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

eastWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

eastWall.setQueueBucket(Bucket.Transparent);

eastWall.setMaterial(eastWallMat);

roomNode.attachChild(eastWall);



Box westWallMesh = new Box(0.5f, 2f, 5f);

Geometry westWall = new Geometry("(Box) Wall 2 - X-5.5", westWallMesh);

westWall.move(-5.5f, 2.5f, 0);

Material westWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

westWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

westWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

westWall.setQueueBucket(Bucket.Transparent);

westWall.setMaterial(westWallMat);

roomNode.attachChild(westWall);



Box northWallMesh = new Box(5f, 2f, 0.5f);

Geometry northWall = new Geometry("(Box) Wall 3 - Z5.5", northWallMesh);

northWall.move(0, 2.5f, 5.5f);

Material northWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

northWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

northWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

northWall.setQueueBucket(Bucket.Transparent);

northWall.setMaterial(northWallMat);

roomNode.attachChild(northWall);



Box southWallMesh = new Box(5f, 2f, 0.5f);

Geometry southWall = new Geometry("(Box) Wall 4 - Z-5.5", southWallMesh);

southWall.move(0, 2.5f, -5.5f);

Material southWallMat = new Material(assetManager, “Common/MatDefs/Misc/SolidColor.j3md”);

southWallMat.setColor(“m_Color”, ColorRGBA.Orange.clone());

southWallMat.getAdditionalRenderState().setBlendMode(BlendMode.Alpha);

southWall.setQueueBucket(Bucket.Transparent);

southWall.setMaterial(southWallMat);

roomNode.attachChild(southWall);



// We must add a light to make the model visible

DirectionalLight sun = new DirectionalLight();

sun.setDirection(new Vector3f(-0.1f, -0.7f, -1.0f).normalizeLocal());

rootNode.addLight(sun);



// Create input

inputManager.addMapping(“MoveRight”, new KeyTrigger(KeyInput.KEY_L));

inputManager.addMapping(“MoveLeft”, new KeyTrigger(KeyInput.KEY_J));

inputManager.addMapping(“MoveUp”, new KeyTrigger(KeyInput.KEY_I));

inputManager.addMapping(“MoveDown”, new KeyTrigger(KeyInput.KEY_K));



inputManager.addListener(analogListener, new String[]{

“MoveRight”, “MoveLeft”, “MoveUp”, “MoveDown”

});

}

private AnalogListener analogListener = new AnalogListener() {

public void onAnalog(String name, float value, float tpf) {

if (name.equals(“MoveRight”)) {

geom1.move(2 * tpf, 0, 0);

}

if (name.equals(“MoveLeft”)) {

geom1.move(-2 * tpf, 0, 0);

}

if (name.equals(“MoveUp”)) {

geom1.move(0, 0, 2 * tpf);

}

if (name.equals(“MoveDown”)) {

geom1.move(0, 0, -2 * tpf);

}

}

};



@Override

public void simpleUpdate(float tpf) {

rootNode.updateGeometricState();



cam.lookAt(geom1.getWorldTranslation(), Vector3f.UNIT_Y);



// Build a ray that point the opposite of the camera.

Ray ray = new Ray(geom1.getWorldTranslation(), cam.getDirection().negate());

CollisionResults results = new CollisionResults();



roomNode.collideWith(ray, results);



for (int i = 0; i < results.size(); i++) {

System.out.println(results.getCollision(i).getGeometry().getName());



Geometry geom = results.getCollision(i).getGeometry();

OccludeFader o = geom.getControl(OccludeFader.class);



if(o == null) {

if(geom.getName().contains(“Box”)) {

geom.addControl(new OccludeFader(true, geom.getMaterial()));

}

else {

geom.addControl(new OccludeFader(false, geom.getMaterial()));

}

} else {

o.setFadeOut();

}

}

}

}[/java]



OccludeFader:

[java]package com.flah.playground.wallOcc;



import com.jme3.material.Material;

import com.jme3.math.ColorRGBA;

import com.jme3.renderer.RenderManager;

import com.jme3.renderer.ViewPort;

import com.jme3.scene.Spatial;

import com.jme3.scene.control.AbstractControl;

import com.jme3.scene.control.Control;



public class OccludeFader extends AbstractControl {



private static final float FADETHRESHOLD = 0.4f;

private static final float FADESPEED = 1f;

private float currentFade;

private boolean isFadingOut;



private boolean isSolidColor;

private Material material;



public OccludeFader(boolean isSolidColor, Material mat) {

this.isSolidColor = isSolidColor;

this.material = mat;

currentFade = 1f;

isFadingOut = true;

}



@Override

protected void controlUpdate(float tpf) {

if(isFadingOut) {

currentFade -= FADESPEED * tpf;

if(currentFade < FADETHRESHOLD) {

currentFade = FADETHRESHOLD;

}

}

else {

currentFade += FADESPEED * tpf;

if(currentFade > 1f) {

currentFade = 1f;

}

}



if(isSolidColor) {

ColorRGBA color = (ColorRGBA) material.getParam(“m_Color”).getValue();

color.a = currentFade;

}

else {

ColorRGBA diffuse = (ColorRGBA) material.getParam(“m_Diffuse”).getValue();

diffuse.a = currentFade;

}



if(currentFade == 1f) {

spatial.removeControl(this);

}

isFadingOut = false;

}



public void setFadeOut() {

isFadingOut = true;

}



@Override

protected void controlRender(RenderManager rm, ViewPort vp) {



}



@Override

public Control cloneForSpatial(Spatial spatial) {

return null;

}

}[/java]

2 Likes