Hey all,
I was wondering if somebody could help me out with some problems I'm having with awt canvas resizing and picking problems.
First, the code:
package test.pickprobs;
import java.awt.Canvas;
import java.awt.Container;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.concurrent.Callable;
import javax.swing.JFrame;
import com.jme.bounding.BoundingBox;
import com.jme.bounding.BoundingVolume;
import com.jme.image.Texture;
import com.jme.input.KeyInput;
import com.jme.intersection.BoundingPickResults;
import com.jme.intersection.PickResults;
import com.jme.math.FastMath;
import com.jme.math.Ray;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.scene.Geometry;
import com.jme.scene.Node;
import com.jme.scene.Text;
import com.jme.scene.shape.Box;
import com.jme.scene.shape.Quad;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.GameTaskQueue;
import com.jme.util.GameTaskQueueManager;
import com.jme.util.TextureManager;
import com.jmex.awt.JMECanvas;
import com.jmex.awt.SimpleCanvasImpl;
import com.jmex.awt.input.AWTMouseInput;
public class TestPickProbs {
public static void main(String[] args) {
JFrame frame = new JFrame();
frame.setSize(1024, 768);
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
final TestPickProbs con = new TestPickProbs(frame.getContentPane());
frame.setVisible(true);
try {
Thread.sleep(1000);
} catch (Exception e) {
}
con.createQuad();
}
private Container parent;
private Canvas canvas;
private PickProbsImplementor implementor;
private JMECanvas jmeCanvas;
public TestPickProbs(Container par) {
this.parent = par;
int width = 1280;
int height = 1024;
canvas = DisplaySystem.getDisplaySystem("lwjgl").createCanvas(width, height);
implementor = new PickProbsImplementor(width, height);
KeyInput.setProvider(KeyInput.INPUT_AWT);
AWTMouseInput.setup(canvas, false);
jmeCanvas = (JMECanvas) canvas;
jmeCanvas.setImplementor(implementor);
jmeCanvas.setUpdateInput(true);
canvas.setBounds(0,0, par.getWidth(),par.getHeight());
new Thread() {
{
setDaemon(true);
}
public void run() {
while (true) {
canvas.repaint();
yield();
}
}
}.start();
Callable<?> exe = new Callable() {
public Object call() {
canvas.setSize(canvas.getWidth(), canvas.getHeight() + 1);
canvas.setSize(canvas.getWidth(), canvas.getHeight() - 1);
return null;
}
};
GameTaskQueueManager.getManager().getQueue(GameTaskQueue.RENDER)
.enqueue(exe);
addListeners();
parent.add(canvas);
}
private void addListeners() {
parent.addComponentListener(new ComponentAdapter() {
@Override
public void componentResized(ComponentEvent e) {
updateCanvasSize();
}
});
canvas.addMouseMotionListener(new MouseAdapter() {
@Override
public void mouseMoved(MouseEvent e) {
implementor.hover(e.getX(), canvas.getHeight()-e.getY());
}
});
}
public void createQuad() {
Quad q1 = new Quad("quad", 70f, 70f);
q1.setModelBound(new BoundingBox());
q1.updateModelBound();
q1.setRandomColors();
implementor.getRootNode().attachChild(q1);
implementor.getRootNode().updateModelBound();
implementor.centerCamera(implementor.getRootNode());
implementor.doUpdate();
}
public void updateCanvasSize() {
canvas.setSize(parent.getWidth(), parent.getHeight());
implementor.resizeCanvas(parent.getWidth(), parent.getHeight());
}
class PickProbsImplementor extends SimpleCanvasImpl {
public PickProbsImplementor(int width, int height) {
super(width, height);
}
Vector3f focus = new Vector3f();
private Text pickText;
private Text posText;
@Override
public void simpleSetup() {
cam.setLocation(new Vector3f(0f, 0f, 30f));
cam.lookAt(new Vector3f(), Vector3f.UNIT_Y);
Box b = new Box("box", new Vector3f(-1.3f, -1.3f, -1.3f), new Vector3f(1.3f, 1.3f, 1.3f));
b.setRandomColors();
getRootNode().attachChild(b);
rootNode.setModelBound(new BoundingBox());
rootNode.updateModelBound();
rootNode.updateWorldBound();
cam.setParallelProjection(true);
rootNode.attachChild(createGrid());
Callable<?> exe = new Callable() {
public Object call() {
resizeCanvas(width, height);
return null;
}
};
GameTaskQueueManager.getManager().render(exe);
setupText();
}
private void setupText() {
AlphaState as = renderer.createAlphaState();
as.setBlendEnabled(true);
as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
as.setDstFunction(AlphaState.DB_ONE);
as.setTestEnabled(true);
as.setTestFunction(AlphaState.TF_GREATER);
as.setEnabled(true);
TextureState ts = renderer.createTextureState();
ts.setTexture(
TextureManager.loadTexture(
TestPickProbs.class.getClassLoader().getResource(Text.DEFAULT_FONT),
Texture.MM_LINEAR,
Texture.FM_LINEAR));
ts.setEnabled(true);
posText = new Text("text", "Mouse Position: ");
posText.setLocalTranslation(new Vector3f(1,60,0));
posText.setRenderState(as);
posText.setRenderState(ts);
posText.setLightCombineMode(LightState.OFF);
pickText = new Text("text", "Pick: ");
pickText.setLocalTranslation(new Vector3f(1,42,0));
pickText.setRenderState(as);
pickText.setRenderState(ts);
pickText.setLightCombineMode(LightState.OFF);
rootNode.attachChild(posText);
rootNode.attachChild(pickText);
cam.update();
rootNode.updateGeometricState(0.0f, true);
rootNode.updateRenderState();
}
@Override
public void resizeCanvas(int width, int height) {
super.resizeCanvas(width, height);
this.width = width;
this.height = height;
updateCamera();
}
public void centerCamera(Node node) {
node.setModelBound(new BoundingBox());
node.updateModelBound();
node.updateWorldBound();
focus = node.getWorldBound().getCenter();//.add(node.getWorldTranslation());
float diameter = 50f;
if(node.getWorldBound().getType() == BoundingVolume.BOUNDING_BOX) {
BoundingBox bb = (BoundingBox) node.getWorldBound();
diameter = Math.abs(bb.xExtent*2) + 20f;
}
System.out.println("diamter: " + diameter);
Vector3f offset = new Vector3f(0,0,diameter);
Vector3f campos = focus.add(offset);
cam.setLocation(campos);
cam.lookAt(focus, Vector3f.UNIT_Y);
updateCamera();
cam.onFrameChange();
node.attachChild(new Box("test", focus, 1f, 1f, 1f));
}
public void updateCamera() {
if(cam == null) {
return;
}
float dist = cam.getLocation().distance(focus);
float mod = dist;
float aspect = (float) width/height;
cam.setFrustum(0f, 1000f, -mod * aspect, mod * aspect, -mod, mod);
cam.onFrameChange();
cam.onFrustumChange();
}
public PickResults hover(int x, int y) {
float x_ = (float) x * 1.0f;
float y_ = (float) y * 1.0f;
Vector3f near = cam.getWorldCoordinates(new Vector2f(x_,y_), 0);
Vector3f far = cam.getWorldCoordinates(new Vector2f(x_,y_), 1);
posText.print("Screen["+x+","+y+"] ---> World[" + near.x + ", " + near.y + ", " + near.z+ "]");
Ray ray = new Ray(near, far);
PickResults results = new BoundingPickResults();
results.setCheckDistance(true);
rootNode.findPick(ray, results);
String pstr = "Pick: ";
for(int i=0; i < results.getNumber(); i++) {
if(i != 0)
pstr += ", ";
pstr += results.getPickData(i).getTargetMesh().getParentGeom().getName();
}
pickText.print(pstr);
return results;
}
private Geometry createGrid() {
int GRID_LINES = 10;
float GRID_SPACING = 10;
Vector3f[] vertices = new Vector3f[GRID_LINES * 2 * 2];
float edge = GRID_LINES / 2 * GRID_SPACING;
for (int ii = 0, idx = 0; ii < GRID_LINES; ii++) {
float coord = (ii - GRID_LINES / 2) * GRID_SPACING;
vertices[idx++] = new Vector3f(-edge, 0f, coord);
vertices[idx++] = new Vector3f(+edge, 0f, coord);
vertices[idx++] = new Vector3f(coord, 0f, -edge);
vertices[idx++] = new Vector3f(coord, 0f, +edge);
}
Geometry grid = new com.jme.scene.Line("grid", vertices, null,
null, null);
grid.getBatch(0).getDefaultColor().set(ColorRGBA.darkGray.clone());
grid.setLocalRotation(grid.getLocalRotation().fromAngleAxis(90 * FastMath.DEG_TO_RAD, Vector3f.UNIT_X));
return grid;
}
@Override
public void doRender() {
super.doRender();
}
}
}
I've put everything in a single class, so you should be able to just throw it in your favorite IDE and run.
And some screen shots (white 'x' is mouse position):
Screenshot 1: Mouse over grid and quad, but only grid is picked.
Screenshot 2: Mouse moved slightly left from screenshot 1, now both grid and quad are picked.
So, we've got a pretty basic scene. The root node has 3 children: a small box in the center, a quad, and the line grid from Ren's particle editor.
1. On the initial load I've noticed that the world coordinates range from -1 to 1, for x and y. After resizing the window they range (correctly?) from frustum left to frustum right along x, and frustum top to frustum bottom along y. Anybody know whats going on here?
2. My major problem is that it seems that picking is off by a bit. As you can see in screenshot 1, the mouse is over the quad, however only the grid has been picked. Screenshot 2 shows that if you move the mouse further towards the center the quad also gets picked.
Due to the use of parallel projection I'm doing some pretty funky stuff with the frustums (see updateCamera()). This allows me to "zoom" when in parallel projection by changing the frustums to reflect the camera's distance to a focus point.
Also, I had to change FastMath.FLT_EPSILON from 1.1920928955078125E-7f to 1.1920928955078125E-8f to get around the "This matrix cannot be inverted." exception I was getting from Camera.getWorldCoordinates while in parallel projection mode.
I've spent about a week now revisiting these problems over and over and can't seem to get anywhere, so any help would be much appreciated. And if I've left anything out or more info is needed please let me know.
Thanks.