Season's Greetings!
This is my first chance to give back to the community. I hope some people find it useful.
As I'm extremely new to jME I would appreciate an code review to see what I've done wrong or poorly and to get it into shape for sharing.
Navigator class:
The Navigator is a combination compass and waypoint display. The North indicator 'card' rotates with changes in the camera's heading (the direction that it's pointing) relative to North; the outer waypoint ring rotates with changes in the camera's location realtive to the waypoint (the bearing from camera to waypoint). Both North and the waypoint are user-settable. If no waypoint is set (null) then the outer waypoint ring is not visible.
see attached screenshot.
Revised code can be found in later post
You'll also need the two Compass images: CompassNorth.png and CompassWaypoint.png, attached below
Here is the screenshot:
nymon said:
Thanks for updating this! You should also remove the code from the first post, save people some time by not grabbing the wrong one.
roger wilco!
Really nice job! 8) The perfect place for this is the code snippets section of the wiki, please add it there.
nymon said:
Really nice job! 8) The perfect place for this is the code snippets section of the wiki, please add it there.
nymon, thanks for the compliment!
I'll put in the snippets page after someone finds and I fix the defects that *must* be lurking there. :D
Will someone please test this and tell me if compass textures are blending correctly?
I'm trying to make a HUD and I can't get the alpha channel of my textures to blend, so I tried this class to see if it works and I'm seeing the same problem. What I see is the waypoint circle, but no 'N' in the middle, and if you move the camera in the test so that the compass is covering the sphere, you will see that the alpha channel of the textures is black, meaning its not blending -OR- you may not see this and it turns out to be a driver/card problem on my end.
But I need a sanity check here pleaseā¦
Sorry I didn't see this til now nymon:
Looks like the blend states are a little off here, when I both to this it works fine:
final BlendState blendState = getRenderer().createBlendState();
blendState.setSourceFunctionAlpha( SourceFunction.SourceAlpha );
blendState.setDestinationFunctionAlpha( DestinationFunction.OneMinusSourceAlpha );
blendState.setEnabled( true );
blendState.setTestEnabled( true );
blendState.setBlendEnabled( true );
blendState.setReference( 0.1f );
blendState.setTestFunction( BlendState.TestFunction.GreaterThanOrEqualTo );
Thanks!
nymon said:
Will someone please test this and tell me if compass textures are blending correctly?
I'm trying to make a HUD and I can't get the alpha channel of my textures to blend, so I tried this class to see if it works and I'm seeing the same problem. What I see is the waypoint circle, but no 'N' in the middle, and if you move the camera in the test so that the compass is covering the sphere, you will see that the alpha channel of the textures is black, meaning its not blending -OR- you may not see this and it turns out to be a driver/card problem on my end.
But I need a sanity check here please...
Sorry for the delay! I don't recall getting an email notification about a new post.
I've seen what you're seeing but not in the code I posted.
Nevertheless, I took basixs' code and it works for me, too, so I will defer to his greater experience and revise the navigator code.
It is found below. Images are found in the first post.
Navigator class
//~--- non-JDK imports
import com.jme.image.Texture;
import com.jme.math.FastMath;
import com.jme.math.Quaternion;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.renderer.Renderer;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TexCoords;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.BlendState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
import com.jme.util.geom.BufferUtils;
//~--- JDK imports
import java.nio.FloatBuffer;
/**
* The Navigator is a combination compass and waypoint display.
* The North indicator 'card' rotates with changes in the camera's heading
* (the direction that it's pointing) relative to North; the outer waypoint
* ring rotates with changes in the camera's location relative to the waypoint
* (the bearing from camera to waypoint). Both North and the waypoint are
* user-settable. If no waypoint is set (null) then the outer waypoint
* ring is not visible.
* * Note: Don't forget to call simpleUpdate from the game simpleUpdate()
* @author Kropotkin (with lots of code and comments from HUD tutorial)
*/
public class Navigator {
protected static DisplaySystem __display = DisplaySystem.getDisplaySystem();
// in the case of the compass, 'up' is along the Z axis.
protected Vector3f _upVector = new Vector3f(0, 0, 1);
// for the waypoint, it's along the Y axis
protected Vector3f _waypointUpVector = new Vector3f(0, 1, 0);
// default values
protected Vector3f _northVec = new Vector3f(0, 0, -10).normalizeLocal();
protected Vector2f _north2f = new Vector2f(0, -10).normalizeLocal();
protected Quaternion _compassQuat = new Quaternion();
protected Quaternion _waypointQuat = new Quaternion();
protected Vector2f _camDirection2f;
protected Vector2f _camLocation2f;
protected Vector3f _cameraDirectionVec;
protected Vector3f _cameraLocationVec;
protected float _compassLocalScale;
protected float _compassLocationX;
protected float _compassLocationY;
// inner compass
protected Node _compassNode;
protected Quad _compassQuad;
protected int _compassTextureHeight;
protected int _compassTextureWidth;
private Node _parentNode;
// outer waypoint
protected Vector3f _waypoint;
protected Vector2f _waypoint2f;
protected Node _waypointNode;
protected Quad _waypointQuad;
protected int _waypointTextureHeight;
protected int _waypointTextureWidth;
public Navigator(float localScale, Node parentNode) {
_compassLocalScale = localScale;
_parentNode = parentNode;
_compassNode = new Node("_compassNode");
_compassQuad = new Quad("_compassQuad", 512f, 512f);
// create the texture state to handle the texture
final TextureState ts = __display.getRenderer().createTextureState();
// load the image bs a texture (the image should be placed in the same directory bs this class)
final Texture texture = TextureManager.loadTexture(
getClass().getResource("/data/CompassNorth.png"),
Texture.MinificationFilter.Trilinear, // of no use for the quad
Texture.MagnificationFilter.Bilinear, // of no use for the quad
1.0f, true);
// set the texture for this texture state
ts.setTexture(texture);
// initialize texture width
_compassTextureWidth = ts.getTexture().getImage().getWidth();
// initialize texture height
_compassTextureHeight = ts.getTexture().getImage().getHeight();
// activate the texture state
ts.setEnabled(true);
// correct texture application:
final FloatBuffer texCoords = BufferUtils.createVector2Buffer(4);
// coordinate (0,0) for vertex 0
texCoords.put(getUForPixel(0)).put(getVForPixel(0));
// coordinate (0,40) for vertex 1
texCoords.put(getUForPixel(0)).put(getVForPixel(512));
// coordinate (40,40) for vertex 2
texCoords.put(getUForPixel(512)).put(getVForPixel(512));
// coordinate (40,0) for vertex 3
texCoords.put(getUForPixel(512)).put(getVForPixel(0));
// assign texture coordinates to the quad
_compassQuad.setTextureCoords(new TexCoords(texCoords));
// apply the texture state to the quad
_compassQuad.setRenderState(ts);
// to handle texture transparency:
// create a blend state
final BlendState bs = __display.getRenderer().createBlendState();
bs.setSourceFunctionAlpha( BlendState.SourceFunction.SourceAlpha );
bs.setDestinationFunctionAlpha( BlendState.DestinationFunction.OneMinusSourceAlpha );
bs.setEnabled( true );
bs.setTestEnabled( true );
bs.setBlendEnabled( true );
bs.setReference( 0.1f );
bs.setTestFunction( BlendState.TestFunction.GreaterThanOrEqualTo );
// assign the blender state to the quad
_compassQuad.setRenderState(bs);
_compassQuad.setRenderQueueMode(Renderer.QUEUE_ORTHO);
/* does not work to disable light under v0.10 */
LightState ls = __display.getRenderer().createLightState();
ls.setEnabled(false);
_compassQuad.setRenderState(ls);
_compassQuad.setLightCombineMode(Spatial.LightCombineMode.Off);
_compassQuad.updateRenderState();
_compassQuad.setLocalScale(new Vector3f(_compassLocalScale, _compassLocalScale, _compassLocalScale));
_compassNode.attachChild(_compassQuad);
_parentNode.attachChild(_compassNode);
}
public void setWaypoint(Vector3f waypoint) {
_waypoint = waypoint.clone();
_waypoint2f = new Vector2f(_waypoint.x, _waypoint.z);
_waypointNode = new Node("_waypointNode");
_waypointQuad = new Quad("_waypointQuad", 512f, 512f);
// create the texture state to handle the texture
final TextureState ts = __display.getRenderer().createTextureState();
// load the image bs a texture (the image should be placed in the same directory bs this class)
final Texture texture = TextureManager.loadTexture(getClass().getResource("/data/CompassWaypoint.png"),
Texture.MinificationFilter.Trilinear, // of no use for the quad
Texture.MagnificationFilter.Bilinear, // of no use for the quad
1.0f, true);
// set the texture for this texture state
ts.setTexture(texture);
// initialize texture width
_waypointTextureWidth = ts.getTexture().getImage().getWidth();
// initialize texture height
_waypointTextureHeight = ts.getTexture().getImage().getHeight();
// activate the texture state
ts.setEnabled(true);
// correct texture application:
final FloatBuffer texCoords = BufferUtils.createVector2Buffer(4);
// coordinate (0,0) for vertex 0
texCoords.put(getUForPixel(0)).put(getVForPixel(0));
// coordinate (0,40) for vertex 1
texCoords.put(getUForPixel(0)).put(getVForPixel(512));
// coordinate (40,40) for vertex 2
texCoords.put(getUForPixel(512)).put(getVForPixel(512));
// coordinate (40,0) for vertex 3
texCoords.put(getUForPixel(512)).put(getVForPixel(0));
// assign texture coordinates to the quad
_waypointQuad.setTextureCoords(new TexCoords(texCoords));
// apply the texture state to the quad
_waypointQuad.setRenderState(ts);
// to handle texture transparency:
// create a blend state
final BlendState bs = __display.getRenderer().createBlendState();
bs.setSourceFunctionAlpha( BlendState.SourceFunction.SourceAlpha );
bs.setDestinationFunctionAlpha( BlendState.DestinationFunction.OneMinusSourceAlpha );
bs.setEnabled( true );
bs.setTestEnabled( true );
bs.setBlendEnabled( true );
bs.setReference( 0.1f );
bs.setTestFunction( BlendState.TestFunction.GreaterThanOrEqualTo );
// assign the blender state to the quad
_waypointQuad.setRenderState(bs);
_waypointQuad.updateRenderState();
_waypointQuad.setRenderQueueMode(Renderer.QUEUE_ORTHO);
/* does not work to disable light under v0.10 */
LightState ls = __display.getRenderer().createLightState();
_waypointQuad.setRenderState(ls);
_waypointQuad.updateRenderState();
_waypointQuad.setLightCombineMode(Spatial.LightCombineMode.Off);
_waypointQuad.updateRenderState();
_waypointQuad.setLocalScale(new Vector3f(_compassLocalScale, _compassLocalScale, _compassLocalScale));
_waypointNode.attachChild(_waypointQuad);
_parentNode.attachChild(_waypointNode);
}
public int getHeight() {
return Math.round(_compassTextureHeight * _compassLocalScale);
}
public int getWidth() {
return Math.round(_compassTextureWidth * _compassLocalScale);
}
public void setLocation(float locX, float locY) {
_compassLocationX = locX;
_compassLocationY = locY;
_compassQuad.setLocalTranslation(_compassLocationX, _compassLocationY, 0);
if (_waypoint != null) {
_waypointQuad.setLocalTranslation(_compassLocationX, _compassLocationY, 0);
}
}
private float getUForPixel(int xPixel) {
return (float) xPixel / _compassTextureWidth;
}
private float getVForPixel(int yPixel) {
return 1f - (float) yPixel / _compassTextureHeight;
}
public void simpleUpdate(Camera cam) {
// update the inner compass indicator
_cameraDirectionVec = cam.getDirection().normalizeLocal();
_camDirection2f = new Vector2f(_cameraDirectionVec.x, _cameraDirectionVec.z);
_compassQuat.fromAngleNormalAxis(_north2f.angleBetween(_camDirection2f), getUpVector());
_compassQuad.setLocalRotation(_compassQuat);
// update the outer waypoint
if (_waypoint != null) {
_cameraLocationVec = cam.getLocation();
_camLocation2f = new Vector2f(_cameraLocationVec.x, _cameraLocationVec.z);
_waypointQuat.fromAngleNormalAxis(_camLocation2f.angleBetween(_waypoint2f) + FastMath.DEG_TO_RAD * 180,
getUpVector());
_waypointQuad.setLocalRotation(_waypointQuat);
}
}
/**
* @return the _upVector
*/
public Vector3f getUpVector() {
return _upVector;
}
/**
* @param _upVector the _upVector to set
*/
public void setUpVector(Vector3f _upVector) {
this._upVector = _upVector;
}
/**
* @return the _waypointUpVector
*/
public Vector3f getWaypointUpVector() {
return _waypointUpVector;
}
/**
* @param _waypointUpVector the _waypointUpVector to set
*/
public void setWaypointUpVector(Vector3f _waypointUpVector) {
this._waypointUpVector = _waypointUpVector;
}
/**
* @return the _northVec
*/
public Vector3f getNorthVec() {
return _northVec;
}
/**
* @param northVec the _northVec to set
*/
public void setNorthVec(Vector3f northVec) {
this._northVec = northVec;
_north2f.x = _northVec.x;
_north2f.y = _northVec.z;
}
}
TestNavigator class
import com.jme.app.SimpleGame;
import com.jme.bounding.BoundingBox;
import com.jme.math.Vector3f;
import com.jme.renderer.Renderer;
import com.jme.scene.VBOInfo;
import com.jme.scene.shape.Sphere;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
*
* Test the Navigator. Ensure that it does point North and
* that the waypoint, if set, points to the actual waypoint.
* If the waypoint is not set (null) then the outer waypoint ring
* is not displayed.
* The Navigator is a combination compass and waypoint display.
* The North indicator 'card' rotates with changes in the camera's heading
* (the direction that it's pointing) relative to North; the outer waypoint
* ring rotates with changes in the camera's location relative to the waypoint
* (the bearing from camera to waypoint). Both North and the waypoint are
* user-settable. If no waypoint is set (null) then the outer waypoint
* ring is not visible.
* * Note the call to _navigator.simpleUpdate in simpleUpdate()
* @author Kropotkin
*/
public class TestNavigator extends SimpleGame {
private static final Logger logger = Logger
.getLogger(TestNavigator.class.getName());
private Navigator _navigator;
public static void main(String[] args) {
TestNavigator app = new TestNavigator();
app.setConfigShowMode(ConfigShowMode.AlwaysShow);
app.start();
}
@Override
protected void simpleUpdate() {
_navigator.simpleUpdate(cam);
}
@Override
protected void simpleInitGame() {
display.setTitle("Test Navigator");
Vector3f waypoint = new Vector3f(0, 0, -350);
// Add a sphere - it will be our waypoint
Sphere s = new Sphere("Sphere", 12, 12, 12);
s.setModelBound(new BoundingBox());
s.updateModelBound();
// set the location of the sphere to be the waypoint
s.setLocalTranslation(waypoint);
rootNode.attachChild(s);
s.setVBOInfo(new VBOInfo(true));
s.setRenderQueueMode(Renderer.QUEUE_OPAQUE);
// Create the navigator
_navigator = new Navigator(0.25f, rootNode);
// Set north
// same dimension as skybox
_navigator.setNorthVec(new Vector3f(0, 0, -100));
// set the location of the waypoint
// if the waypoint is not set (null) then
// the outer waypoint card is not visible
_navigator.setWaypoint(waypoint);
// get the location of the upper left corner of the screen
// in order to place the navigator on-screen
float locX = _navigator.getWidth() - _navigator.getWidth() / 2;
float locY = display.getHeight() - _navigator.getHeight() + _navigator.getHeight() / 2;
// set navigator's on-screen location
_navigator.setLocation(locX, locY);
}
}
If there are no objections by the end of the weekend, I'll post this on the snippets page per nymon's suggestion.
Thanks!
Thanks for updating this! You should also remove the code from the first post, save people some time by not grabbing the wrong one.