Hey All,
I'm having a little trouble with this one, so I thought I'd come to the experts.
I have a space ship in a system right now and the controls I have are standard WASD. Actually, I used the Flag tutorial pretty heavily for learning the input, so that's essentially what I have right now…except in space. What I'd like to do is re-write the input code so that my space ship moves where I click…but only in the horizantal plane as I do not want and veritical movement at this time.
I searched the toolset we have with jME for this functionality with no luck. I'm not looking for a full "cut-and-paste" solution from anyone as I would really like to learn about this…although, due to me being new to the engine and somewhat java, some heavy hints would be nice.
…what should I look at to get something basic going?
Cheers,
D
This one is easy… you can use algebra to solve for the ray root.
Once you have your mouse ray, you need to find a number N where the Y will result in zero (collides with the horizontal plane)
N = (HorizontalPlaneY - RayY) / RayYslope
Then you can find the collision point:
X = RayX + RayXslope * N
Y = HorizontalPlaneY
Z = RayZ + RayZslope * N
Any reason not to use a Quad as the 'plane' and check for intersection? I will implement something similar in the future and was planning to do it this way. The Quad could also be textured, displaying for example a grid, if the user chooses so, or similar.
Setting down a Quad grid is a good idea…along with Momoko's idea. I'll ponder over it but I'm not sure if I want that Quad visible to the user.
Tell me, would this quad take up resources…because for me, space will be big and I don't want the contruction of this quad to hinder performance if it can be helped.
darrenl said:
Tell me, would this quad take up resources...because for me, space will be big and I don't want the contruction of this quad to hinder performance if it can be helped.
Well Quad is as simple an object as it gets. Does not matter what size you make it, should have next to no impact on the performance. But actually don't decide yet, lets see what others have to say... I am as curious as you about it :)
…shameless bump.
I'm close to this as I'm put in a plane have been able to output x,y,and z coordinates of a mouse click. It's now a question of getting my object to rotate and move to the clicked coordinates.
If anyone else has a better solution, let me know.
retrieve the mouse coordinates --> translate into world coordinates --> restrict y component --> translate into local coordinate system --> set a destination on the object --> update the object with a speed every frame.
here u might wanna control ur update rate so dont use the frame interpolation. do a simple lag translation should be fine.
Using algebra for this not only saves performance but also the work needed to make sure the quad is always under the camera and such. And specific reason why you are not using it?
Well…what I've got so far in my class that controls input is this:
package Proto;
import java.net.URL;
import Proto.actions.DriftAction;
import Proto.actions.ForwardAndBackwardAction;
import Proto.actions.VehicleRotateAction;
import com.jme.image.Texture;
import com.jme.input.AbsoluteMouse;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyBindingManager;
import com.jme.input.KeyInput;
import com.jme.input.MouseInput;
import com.jme.intersection.PickResults;
import com.jme.intersection.TrianglePickResults;
import com.jme.math.FastMath;
import com.jme.math.Matrix3f;
import com.jme.math.Plane;
import com.jme.math.Ray;
import com.jme.math.Triangle;
import com.jme.math.Vector2f;
import com.jme.math.Vector3f;
import com.jme.renderer.Camera;
import com.jme.scene.Controller;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.TriMesh;
import com.jme.scene.shape.Cylinder;
import com.jme.scene.state.AlphaState;
import com.jme.scene.state.TextureState;
import com.jme.system.DisplaySystem;
import com.jme.util.TextureManager;
public class ShipInput extends InputHandler {
// the default action
protected DriftAction drift;
protected TriMesh weapon;
protected Vector2f screenPos;
protected Vector3f worldCoords;
protected Vector3f worldCoords2;
protected Camera cam;
protected ChaseCamera chaser;
protected WalkController walk;
protected static Ship ship = Proto.getShip();
protected Ship enemy = Proto.getEnemy();
protected Node scene = Proto.getScene();
protected InputHandler input = Proto.getInput();
protected AbsoluteMouse am;
@Override
public void update(float time) {
if (!isEnabled())
return;
super.update(time);
// we always want to allow friction to control the drift
drift.performAction(event);
ship.update(time);
input.update(time);
updateMouse();
}
/**
* Supply the node to control and the api that will handle input creation.
*
* @param vehicle
* the node we wish to move
* @param api
* the library that will handle creation of the input.
*/
public ShipInput(Ship ship, String api) {
ShipInput.ship = ship;
// this.enemy = enemy;
// this.scene = scene;
setKeyBindings(api);
setActions(ship);
//Plane p1 = setupPlane();
setupMouse();
walk = new WalkController(ship);
walk.setSpeed(ship.getVelocity());
ship.addController(walk);
}
/**
* creates the keyboard object, allowing us to obtain the values of a
* keyboard as keys are pressed. It then sets the actions to be triggered
* based on if certain keys are pressed (WSAD).
*
* @param api
* the library that will handle creation of the input.
*/
private void setKeyBindings(String api) {
KeyBindingManager keyboard = KeyBindingManager.getKeyBindingManager();
keyboard.set("forward", KeyInput.KEY_W);
keyboard.set("backward", KeyInput.KEY_S);
keyboard.set("turnRight", KeyInput.KEY_D);
keyboard.set("turnLeft", KeyInput.KEY_A);
// keyboard.set("fireBullet", KeyInput.KEY_SPACE);
}
/**
* assigns action classes to triggers. These actions handle moving the node
* forward, backward and rotating it. It also creates an action for drifting
* that is not assigned to key trigger, this action will occur each frame.
*
* @param node
* the node to control.
*/
private void setActions(Ship node) {
ForwardAndBackwardAction forward = new ForwardAndBackwardAction(node,
ForwardAndBackwardAction.FORWARD);
addAction(forward, "forward", true);
ForwardAndBackwardAction backward = new ForwardAndBackwardAction(node,
ForwardAndBackwardAction.BACKWARD);
addAction(backward, "backward", true);
VehicleRotateAction rotateLeft = new VehicleRotateAction(node,
VehicleRotateAction.LEFT);
addAction(rotateLeft, "turnLeft", true);
VehicleRotateAction rotateRight = new VehicleRotateAction(node,
VehicleRotateAction.RIGHT);
addAction(rotateRight, "turnRight", true);
addAction(new FireWeapon(enemy), "fireBullet", KeyInput.KEY_F, false);
// not triggered by keyboard
drift = new DriftAction(node);
}
private void setupMouse() {
// mouse stuff
// Create a new mouse. Restrict its movements to the display screen.
am = new AbsoluteMouse("The Mouse", DisplaySystem.getDisplaySystem()
.getWidth(), DisplaySystem.getDisplaySystem().getHeight());
// Get a picture for my mouse.
TextureState mCursor = DisplaySystem.getDisplaySystem().getRenderer()
.createTextureState();
URL cursorLoc = CakeProto.class.getClassLoader().getResource(
"jmetest/data/cursor/cursor1.png");
Texture t = TextureManager.loadTexture(cursorLoc, Texture.MM_LINEAR,
Texture.FM_LINEAR);
mCursor.setTexture(t);
am.setRenderState(mCursor);
// Make the mouse's background blend with what's already there
AlphaState as = DisplaySystem.getDisplaySystem().getRenderer()
.createAlphaState();
as.setBlendEnabled(true);
as.setSrcFunction(AlphaState.SB_SRC_ALPHA);
as.setDstFunction(AlphaState.DB_ONE_MINUS_SRC_ALPHA);
as.setTestEnabled(true);
as.setTestFunction(AlphaState.TF_GREATER);
am.setRenderState(as);
// Get the mouse input device and assign it to the AbsoluteMouse
// Move the mouse to the middle of the screen to start with
am.setLocalTranslation(new Vector3f(DisplaySystem.getDisplaySystem()
.getWidth() / 2,
DisplaySystem.getDisplaySystem().getHeight() / 2, 0));
// Assign the mouse to an input handler
am.registerWithInputHandler(input);
// Attach Children
scene.attachChild(am);
}
private void updateMouse() {
// try a cylinder or two cones
if (MouseInput.get().isButtonDown(0)) {
Plane p1 = new Plane();
// Plane p2 = new Plane();
// Vector3f loc2 = new Vector3f();
Vector3f t1a = new Vector3f(0, 0, 0); // point a on Triangle 1;
Vector3f t1b = new Vector3f(1, 0, 0); // point b on Triangle 1;
Vector3f t1c = new Vector3f(0, 0, 1); // point c on Triangle 1;
Triangle t1 = new Triangle(t1a, t1b, t1c);
p1.setPlanePoints(t1);
Vector2f screenPos = new Vector2f();
Vector3f loc1 = new Vector3f();
// Get the position that the mouse is pointing to
screenPos.set(am.getHotSpotPosition().x, am.getHotSpotPosition().y);
// Get the world location of that X,Y value
Vector3f worldCoords = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 0);
Vector3f worldCoords2 = DisplaySystem.getDisplaySystem()
.getWorldCoordinates(screenPos, 1);
Vector3f direction = worldCoords2.subtractLocal(worldCoords)
.normalizeLocal();
Ray mouseRay = new Ray(worldCoords, direction);
boolean intersection1 = mouseRay.intersectsWherePlane(p1, loc1);
// boolean intersection2 = mouseRay.intersectsWherePlane(p2, loc2);
System.out.println("intersection? " + intersection1 + " x1="
+ loc1.x + " y1=" + loc1.y + " z1=" + loc1.z);
walk.setDesiredLocation(direction);
}
}
private static class WalkController extends Controller {
private static final long serialVersionUID = 1L;
private Ship ship;
private Vector3f desiredLocation;
private boolean moving = false;
public static final int RIGHT = 0;
public static final int LEFT = 1;
private int modifier = 1;
private Vector3f upAxis = new Vector3f(0, 1, 0);
private static final Matrix3f incr = new Matrix3f();
private static final Matrix3f tempMa = new Matrix3f();
private static final Matrix3f tempMb = new Matrix3f();
private Vector3f temp = new Vector3f();
public WalkController(Ship ship) {
this.ship = ship;
}
public void setDesiredLocation(Vector3f location) {
desiredLocation = location;
moving = true;
}
public Vector3f getDesiredLocation() {
return desiredLocation;
}
//GOT TO WORK ON THIS
/*
public void DesiredRotation() {
float angle = FastMath.RAD_TO_DEG*(ship.getLocalTranslation().angleBetween(directional));
if (angle >0 && angle <=180) {
modifier = 1;
} else if (angle > 180 && angle <= 360) {
modifier = -1;
}
if (ship.getVelocity() < 0) {
incr.fromAngleNormalAxis(-modifier * ship.getTurnSpeed()
* time, upAxis);
} else {
incr.fromAngleNormalAxis(modifier * ship.getTurnSpeed()
* time, upAxis);
}
ship.getLocalRotation().fromRotationMatrix(
incr.mult(ship.getLocalRotation().toRotationMatrix(tempMa),
tempMb));
ship.getLocalRotation().normalize();
ship.setRotateOn(modifier);
}*/
public void update(float time) {
if (!moving) return;
Vector3f directional = desiredLocation.subtract(
ship.getLocalTranslation(), temp);
float length = directional.length();
if (length < FastMath.ZERO_TOLERANCE) {
moving = false;
return;
}
float walkDistance = ship.getVelocity()*time;
if (walkDistance > length) walkDistance = length;
directional.multLocal(walkDistance/length);
ship.getLocalTranslation().addLocal(directional);
}
}
My "ship" does not move when I click the mouse on a position on the screen. It just sits there. I'm still able to use WASD. The idea is to get rid of WASD and just have the "ship" move to the location I click. Essentially, I want all the movement described in the Flag tutorial but just with mouse clicks and no WASD.
...this is now my nemesis.
I figure the more I stare at the code, the more likely the solution will come to me :D
the keyboard input and mouse input are two seperate subsystems. ideally u wanna seperate them into their own packages and sets of classes.
the mouse part should make use of the MouseInputListener interface. make some sort of MouseHandler class that implements that interface, then in side the mouse pressed method, call the method that processes the mouse input information.
That helped a lot neakor. I'm very close and I think I just need to play around with it and tweak. Your suggestion also got me to refactor the project quite a bit as well.
darrenl said:
That helped a lot neakor. I'm very close and I think I just need to play around with it and tweak. Your suggestion also got me to refactor the project quite a bit as well.
glad it helped. good luck ;)
OK…that's what I did and I've got everything working.
Thanks so much for the help.
Neakor…quick clarification.
When you say "create a a MouseHandler that implements the MouseInputListener interface, do you mean this:
public class MouseClickMove implements MouseInputListener
or this:
public class MouseClickMove extends MouseHandler implements MouseInputListener
what i meant is this
CustomMouseHandler implements MouseInputListener.
then when u detect a press, determine what move u need to do and do, ClickMove.perform()
darrenl said:
OK...that's what I did and I've got everything working.
Thanks so much for the help.
np, glad i could help.
Momoko_Fan said:
This one is easy.. you can use algebra to solve for the ray root.
Once you have your mouse ray, you need to find a number N where the Y will result in zero (collides with the horizontal plane)
N = (HorizontalPlaneY - RayY) / RayYslope
Then you can find the collision point:
X = RayX + RayXslope * N
Y = HorizontalPlaneY
Z = RayZ + RayZslope * N
I want to implement this approach but I am having problems :( The problem I am having is the lack of Math skills and not knowing the terminology. This is what I have so far:
float horizontalPlaneY = 0;
float rayY = camera.getLocation().y;
float rayYslope = ? ; // <-- problem here
float N = (horizontalPlaneY - rayY) / rayYslope;
float rayX = camera.getLocation().x;
float rayXslope = ?; // <-- problem again
float X = rayX + rayXslope * N;
float Y = horizontalPlaneY;
float rayZ = camera.getLocation().z;
float rayZslope = ?; // <-- problem again
float Z = rayZ + rayZslope * N;
Are the current values taken correctly - are these what you meant? And what are exactly these slopes? Are these the tangents of corners between the ray vector projections onto different axes and those particular axes?1 If they are, I could really use some help with calculating them. I know how to get the tangent between two vectors i think -
FastMath.tan(mouseRay.getDirection().angleBetween(new Vector3f(0, 0, -1)))
But how to get the projections?
Sigh perhaps I am completely on the wrong path... Anyways, any and all help would be very welcome.
Edit:
1 There can be no angle between "ray vector projections onto different axes and those particular axes". So is it a tangent between particular axes vectors and the ray vector? No... does not sound quite right either... should be projection of vector to some plain? I am so lost :D
yeah…you're starting out like I did. I got it working with a "one liner" that moved my object to where I clicked the mouse.
I don't have the exact code here, but all you have to do is:
- create a plane
- create a ray
- find out where that ray intersects the plane.
- update the object's localRotation with the lookAt functionality.
Take a look at that and see what you can do and I'll put my code in this thread when I get home…unless someone beats me to it.
I do not want to use a plane and using momoko_fan's method, it is not necessary. But walking home from work where I conjures this up, I realized I was talking at least partly gibberish -
Are these the tangents of corners between the ray vector projections onto different axes and those particular axes?
There can be no angle between the two mentioned here. So is it a tangent between particular axes vectors and the ray vector? Math wizards where are you :)
Oh my, it took me such an insane amount of thinking to figure out that was stupifyingly simple indeed When Momoko_fan used the word slope, I did not know what he meant, so I looked up the word from algebra texts. This lead me to all kind of complicated calculations, which indeed DID eventually give me the world coordinates, but with about 4 times more work than what momoko meant initially.
So here is a perfectly simple and working code of how to get world coordinates at a specified plane with mouseray for those fellow newbies who are interested
float planeZ = 0;
float startZ = mouseRay.origin.z;
float endZ = mouseRay.direction.z;
float coef = (planeZ - startZ) / endZ;
float planeX = mouseRay.origin.x + (coef * mouseRay.direction.x);
float planeY = mouseRay.origin.y + (coef * mouseRay.direction.y);
Exactly as posted earlier in the thread once you know what the words mean :) But hey, at least I really beefed up my knowledge of tangents, sines and cosines.