/*
* Copyright (C) 2013-2015 F(X)yz,
* Sean Phillips, Jason Pollastrini and Jose Pereda
* All rights reserved.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.fxyz.tests;
import javafx.animation.Interpolator;
import javafx.animation.PauseTransition;
import javafx.animation.Transition;
import javafx.application.Application;
import javafx.geometry.Point3D;
import javafx.scene.AmbientLight;
import javafx.scene.Group;
import javafx.scene.PerspectiveCamera;
import javafx.scene.PointLight;
import javafx.scene.Scene;
import javafx.scene.SceneAntialiasing;
import javafx.scene.input.KeyCode;
import javafx.scene.layout.StackPane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Sphere;
import javafx.stage.Stage;
import javafx.util.Duration;
import org.fxyz.cameras.CameraTransformer;
import org.fxyz.geometry.Ray;
/**
* A simple app Showing the newly added Ray class.
* <br><p>Clicking on the Scene will spawn a Sphere at that position (origin of Ray)<br>
* Mouse buttons target different nodes Targets are breifly highlighted if an intersection occurs.
*
* Hold Control down to Allow Camera movement without firing.
*
* <br><br>
* RayTest in org.fxyz.tests package has reference on TriangleMesh intersections
*
* </p><br>
*
* @author Jason Pollastrini aka jdub1581
*/
public class SimpleRayTest extends Application {
private final StackPane sceneRoot = new StackPane();
private final PhongMaterial
red = new PhongMaterial(Color.DARKRED),
blue = new PhongMaterial(Color.DARKCYAN),
highlight = new PhongMaterial(Color.CHARTREUSE);
private final Group root = new Group();
private Sphere target1, target2;
private PerspectiveCamera camera;
private final CameraTransformer cameraTransform = new CameraTransformer();
private double mousePosX;
private double mousePosY;
private double mouseOldX;
private double mouseOldY;
private double mouseDeltaX;
private double mouseDeltaY;
private boolean fireRay = true;
private final AmbientLight rayLight = new AmbientLight();
@Override
public void start(Stage primaryStage) throws Exception {
// add cameraTransform so it doesn't affect all nodes
rayLight.getScope().add(cameraTransform);
camera = new PerspectiveCamera(true);
cameraTransform.setTranslate(0, 0, 0);
cameraTransform.getChildren().addAll(camera);
camera.setNearClip(0.1);
camera.setFarClip(1000000.0);
camera.setFieldOfView(42);
camera.setTranslateZ(-5000);
PointLight light = new PointLight(Color.GAINSBORO);
PointLight light2 = new PointLight(Color.YELLOW);
light2.setTranslateY(-2000);
//create a target
target1 = new Sphere(100);
target1.setTranslateX(300);
target1.setTranslateY(300);
target1.setTranslateZ(1000);
target1.setMaterial(red);
// create another target
target2 = new Sphere(100);
target2.setTranslateX(800);
target2.setTranslateY(-1200);
target2.setTranslateZ(-500);
target2.setMaterial(blue);
root.getChildren().addAll(cameraTransform, target1, target2, light, light2, rayLight);
root.setAutoSizeChildren(false);
Scene scene = new Scene((root), 1200, 800, true, SceneAntialiasing.BALANCED);
scene.setCamera(camera);
Stop[] stops = new Stop[]{new Stop(0, Color.BLACK), new Stop(0.5, Color.DEEPSKYBLUE.darker()), new Stop(1.0, Color.BLACK)};
LinearGradient lg = new LinearGradient(0, 0, 0, 1, true, CycleMethod.NO_CYCLE, stops);
scene.setFill(lg);
//First person shooter keyboard movement
scene.setOnKeyPressed(ke -> {
double change = 10.0;
//Add shift modifier to simulate "Running Speed"
if (ke.isShiftDown()) {
change = 50.0;
}
//What key did the user press?
KeyCode keycode = ke.getCode();
//Step 2c: Add Zoom controls
if (keycode == KeyCode.W) {
camera.setTranslateZ(camera.getTranslateZ() + change);
}
if (keycode == KeyCode.S) {
camera.setTranslateZ(camera.getTranslateZ() - change);
}
//Step 2d: Add Strafe controls
if (keycode == KeyCode.A) {
camera.setTranslateX(camera.getTranslateX() - change);
}
if (keycode == KeyCode.D) {
camera.setTranslateX(camera.getTranslateX() + change);
}
// add a flag so we can still move the camera
if (keycode == KeyCode.CONTROL) {
fireRay = false;
}
});
scene.setOnKeyReleased(ke -> {
// release flag
if (ke.getCode().equals(KeyCode.CONTROL)) {
fireRay = true;
}
});
scene.setOnMousePressed(e -> {
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseOldX = e.getSceneX();
mouseOldY = e.getSceneY();
if (fireRay) {
// use PickResult because it is already transformed
Point3D o = e.getPickResult().getIntersectedPoint();
if (e.isPrimaryButtonDown()) {
// set Target and Direction
Point3D t = Point3D.ZERO.add(target2.getTranslateX(), target2.getTranslateY(), target2.getTranslateZ()),
d = t.subtract(o);
//Build the Ray
Ray r = new Ray(o, d);
double dist = t.distance(o);
// If ray intersects node, spawn and animate
if (target2.getBoundsInParent().contains(r.project(dist))) {
animateRayTo(r, target2, Duration.seconds(2));
System.out.println(
"Target Contains Ray!\n"
+ r + "\nTarget Bounds: " + target2.getBoundsInParent()
+ "\nDistance: " + dist + "\n"
);
}
e.consume();
} // repeat for other target as well
else if (e.isSecondaryButtonDown()) {
Point3D tgt = Point3D.ZERO.add(target1.getTranslateX(), target1.getTranslateY(), target1.getTranslateZ()),
dir = tgt.subtract(o);
Ray r = new Ray(o, dir);
double dist = tgt.distance(o);
if (target1.getBoundsInParent().contains(r.project(dist))) {
animateRayTo(r, target1, Duration.seconds(2));
System.out.println(
"Target Contains Ray: "
+ target1.getBoundsInParent().contains(r.project(dist)) + "\n"
+ r + "\nTarget Bounds: " + target1.getBoundsInParent()
+ "\nDistance: " + dist + "\n"
);
}
e.consume();
}
}
});
scene.setOnMouseDragged(e -> {
if (!fireRay) {
mouseOldX = mousePosX;
mouseOldY = mousePosY;
mousePosX = e.getSceneX();
mousePosY = e.getSceneY();
mouseDeltaX = (mousePosX - mouseOldX);
mouseDeltaY = (mousePosY - mouseOldY);
double modifier = 10.0;
double modifierFactor = 0.1;
if (e.isControlDown()) {
modifier = 0.1;
}
if (e.isShiftDown()) {
modifier = 50.0;
}
if (e.isPrimaryButtonDown()) {
cameraTransform.ry.setAngle(((cameraTransform.ry.getAngle() + mouseDeltaX * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // +
cameraTransform.rx.setAngle(((cameraTransform.rx.getAngle() - mouseDeltaY * modifierFactor * modifier * 2.0) % 360 + 540) % 360 - 180); // -
} else if (e.isSecondaryButtonDown()) {
double z = camera.getTranslateZ();
double newZ = z + mouseDeltaX * modifierFactor * modifier;
camera.setTranslateZ(newZ);
} else if (e.isMiddleButtonDown()) {
cameraTransform.t.setX(cameraTransform.t.getX() + mouseDeltaX * modifierFactor * modifier * 0.3); // -
cameraTransform.t.setY(cameraTransform.t.getY() + mouseDeltaY * modifierFactor * modifier * 0.3); // -
}
}
});
primaryStage.setTitle("Hello Ray! Animated Visual of a Ray casting");
primaryStage.setScene(scene);
primaryStage.show();
}
/**
* Creates and launches a custom Transition animation
*
* @param r The Ray that holds the info
* @param tx to x
* @param ty to y
* @param tz to z
* @param dps distance per step to move ray
* @param time length of animation
*/
private void animateRayTo(final Ray r, final Sphere target, final Duration time) {
final Transition t = new Transition() {
protected Ray ray;
protected Sphere s;
protected double dist;
{
this.ray = r;
this.s = new Sphere(5);
s.setTranslateX((ray.getOrigin()).getX());
s.setTranslateY((ray.getOrigin()).getY());
s.setTranslateZ((ray.getOrigin()).getZ());
s.setMaterial(highlight);
rayLight.getScope().add(s);
this.dist = ray.getOrigin().distance(
Point3D.ZERO.add(target.getTranslateX(), target.getTranslateY(), target.getTranslateZ())
);
setCycleDuration(time);
this.setInterpolator(Interpolator.LINEAR);
this.setOnFinished(e -> {
if (target.getBoundsInParent().contains(ray.getPosition())) {
target.setMaterial(highlight);
// PauseTransition for delay
PauseTransition t = new PauseTransition(Duration.millis(750));
t.setOnFinished(pe -> {
reset();
root.getChildren().removeAll(s);
s = null;
});
t.playFromStart();
}
});
root.getChildren().add(s);
}
@Override
protected void interpolate(double frac) {
// frac-> 0.0 - 1.0
// project ray
ray.project(dist * frac);
// set the sphere to ray position
s.setTranslateX(ray.getPosition().getX());
s.setTranslateY(ray.getPosition().getY());
s.setTranslateZ(ray.getPosition().getZ());
}
};
t.playFromStart();
}
// resets materisl on targets
private void reset() {
target1.setMaterial(red);
target2.setMaterial(blue);
}
/**
* @param args the command line arguments
*/
public static void main(String[] args) {
launch(args);
}
}