This is great, thanks.
I built a small test program, where everything is on the XZ plane (the camera is looking straight down):
The red line is the direction the turret is facing, the red spheres represent the different ships’ radiuses.
The blue lines should be the maximum turn angle (which is 60° to either side).
But while it works pretty well near the red line (the 5.16° to the edge of the smallest sphere seem to be ok and it also successfully detects if the red line hits a sphere), the ship to the right returns an asin(sinTurn) of 44.26°, although it is outside the 60° range.
Here is my test case:
import com.jme3.app.SimpleApplication;
import com.jme3.font.BitmapText;
import com.jme3.material.Material;
import com.jme3.material.RenderState;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Quaternion;
import com.jme3.math.Vector3f;
import com.jme3.renderer.RenderManager;
import com.jme3.renderer.ViewPort;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.control.AbstractControl;
import com.jme3.scene.control.BillboardControl;
import com.jme3.scene.shape.Line;
import com.jme3.scene.shape.Sphere;
public class TargetingShipsInFoV extends SimpleApplication {
private float turretMaxTurnAngle = FastMath.DEG_TO_RAD * 60f;
private Node turret;
public static void main(String[] args) {
TargetingShipsInFoV app = new TargetingShipsInFoV();
app.start();
}
@Override
public void simpleInitApp() {
flyCam.setMoveSpeed(50);
cam.setLocation(Vector3f.UNIT_Y.mult(75));
cam.lookAt(Vector3f.ZERO, Vector3f.UNIT_Y);
createTurret();
createShips();
}
private void createTurret() {
// Create geometries
Geometry geoDir = new Geometry("geoDir",
new Line(Vector3f.ZERO, Vector3f.UNIT_Z.mult(100)));
Geometry geoBoundry1 = new Geometry("geoBoundry1",
new Line(Vector3f.ZERO, new Quaternion().fromAngles(0, turretMaxTurnAngle, 0).mult(Vector3f.UNIT_Z.mult(100))));
Geometry geoBoundry2 = new Geometry("geoBoundry2",
new Line(Vector3f.ZERO, new Quaternion().fromAngles(0, -turretMaxTurnAngle, 0).mult(Vector3f.UNIT_Z.mult(100))));
// Create materials
geoDir.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
geoBoundry1.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
geoBoundry2.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
geoDir.getMaterial().setColor("Color", ColorRGBA.Red);
geoBoundry1.getMaterial().setColor("Color", ColorRGBA.Blue);
geoBoundry2.getMaterial().setColor("Color", ColorRGBA.Blue);
// Put everything in node
turret = new Node("turret");
turret.attachChild(geoDir);
rootNode.attachChild(geoBoundry1);
rootNode.attachChild(geoBoundry2);
rootNode.attachChild(turret);
// Set default orientation
turret.lookAt(Vector3f.UNIT_Z, Vector3f.UNIT_Y);
// Let the turret rotate to view calculation
turret.addControl(new AbstractControl() {
@Override
protected void controlUpdate(float tpf) {
spatial.rotate(0, tpf * 0.1f, 0);
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
});
}
private void createShips() {
int targets = 10;
for (int i = 0; i < targets; i++) {
Quaternion quat = new Quaternion().fromAngleAxis(FastMath.TWO_PI / (float) targets * (float) i, Vector3f.UNIT_Y);
createShip(quat.mult(Vector3f.UNIT_Z.mult(10 * (float) Math.pow(1.15f, i))), i + 1);
}
}
private void createShip(final Vector3f shipPos, final float shipRadius) {
// Create sphere to show radius
Geometry ship = new Geometry("ship", new Sphere(12, 24, shipRadius));
ship.setMaterial(new Material(assetManager, "Common/MatDefs/Misc/Unshaded.j3md"));
ColorRGBA color = new ColorRGBA(ColorRGBA.Red);
color.a = 0.2f;
ship.getMaterial().setColor("Color", color);
ship.getMaterial().getAdditionalRenderState().setBlendMode(RenderState.BlendMode.Alpha);
ship.setLocalTranslation(shipPos);
// Build text to show targeting status or angle
BitmapText text = new BitmapText(guiFont, false);
text.setSize(1.5f);
text.setLocalTranslation(shipPos);
text.addControl(new BillboardControl());
// Add control to automatically update text
text.addControl(new AbstractControl() {
@Override
protected void controlUpdate(float tpf) {
Vector3f turretPos = turret.getWorldTranslation();
Vector3f dir = turret.getWorldRotation().mult(Vector3f.UNIT_Z);
Vector3f relative = shipPos.subtract(turretPos);
float distance = dir.dot(relative);
Vector3f projected = turretPos.add(dir.mult(distance));
float distVec = projected.subtract(shipPos).length();
float sinTurn = (projected.subtract(shipPos).length() - shipRadius) / relative.length();
// also check for distance >= 0 to avoid targeting ships behind the turret
if (distance >= 0 && sinTurn <= FastMath.sin(turretMaxTurnAngle)) {
if (distVec * distVec <= shipRadius * shipRadius) {
((BitmapText) spatial).setText("can hit");
} else {
((BitmapText) spatial).setText("angle=" + String.format("%4.2f", FastMath.RAD_TO_DEG * FastMath.asin(sinTurn)));
}
} else {
((BitmapText) spatial).setText("out of reach");
}
}
@Override
protected void controlRender(RenderManager rm, ViewPort vp) {
}
});
// Attach both to rootNode
rootNode.attachChild(ship);
rootNode.attachChild(text);
}
}
I don’t know what’s wrong with the code because I admittedly don’t understand all the math you put in that short paragraph… I would really appreciate if you could explain that in more detail