parent
e5d8a4028d
commit
ddc3a6ae47
@ -1,7 +1,260 @@
|
|||||||
package visualiser.layout;
|
package visualiser.layout;
|
||||||
|
|
||||||
|
import javafx.beans.value.ChangeListener;
|
||||||
|
import javafx.collections.ListChangeListener;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.scene.Group;
|
||||||
|
import javafx.scene.PerspectiveCamera;
|
||||||
|
import javafx.scene.SubScene;
|
||||||
|
import javafx.scene.input.PickResult;
|
||||||
|
import javafx.scene.layout.Pane;
|
||||||
|
import javafx.scene.paint.Color;
|
||||||
|
import javafx.scene.shape.Shape3D;
|
||||||
|
import javafx.scene.transform.Rotate;
|
||||||
|
import javafx.scene.transform.Translate;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by hba56 on 9/09/17.
|
* Control for rendering 3D objects visible through a PerspectiveCamera. Implements Adapter Pattern to
|
||||||
|
* interface with camera, and allows clients to add shapes to the scene. All scenes contain sea plane and
|
||||||
|
* sky box, whose textures are set with special methods.
|
||||||
*/
|
*/
|
||||||
public class View3D {
|
public class View3D extends Pane {
|
||||||
}
|
/**
|
||||||
|
* Container for group and camera
|
||||||
|
*/
|
||||||
|
private SubScene scene;
|
||||||
|
/**
|
||||||
|
* Observable list of renderable items
|
||||||
|
*/
|
||||||
|
private ObservableList<Subject3D> items;
|
||||||
|
/**
|
||||||
|
* Map for selecting Subject3D from Shape3D
|
||||||
|
*/
|
||||||
|
private Map<Shape3D, Subject3D> selectionMap;
|
||||||
|
/**
|
||||||
|
* Subject tracked by camera
|
||||||
|
*/
|
||||||
|
private Subject3D target;
|
||||||
|
/**
|
||||||
|
* Rendering container for shapes
|
||||||
|
*/
|
||||||
|
private Group world;
|
||||||
|
/**
|
||||||
|
* Near limit of view frustum
|
||||||
|
*/
|
||||||
|
private double nearClip;
|
||||||
|
/**
|
||||||
|
* Far limit of view frustum
|
||||||
|
*/
|
||||||
|
private double farClip;
|
||||||
|
/**
|
||||||
|
* Camera origin
|
||||||
|
*/
|
||||||
|
private Translate pivot;
|
||||||
|
/**
|
||||||
|
* Distance of camera from pivot point
|
||||||
|
*/
|
||||||
|
private Translate distance;
|
||||||
|
/**
|
||||||
|
* Angle along ground between z-axis and camera
|
||||||
|
*/
|
||||||
|
private Rotate yaw;
|
||||||
|
/**
|
||||||
|
* Angle between ground plane and camera direction
|
||||||
|
*/
|
||||||
|
private Rotate pitch;
|
||||||
|
/**
|
||||||
|
* Single listener for subject heading changes
|
||||||
|
*/
|
||||||
|
private ChangeListener<? super Number> pivotHeading = (o, prev, curr) -> yaw.setAngle((double)curr);
|
||||||
|
/**
|
||||||
|
* Single listener for subject position (x) changes
|
||||||
|
*/
|
||||||
|
private ChangeListener<? super Number> pivotX = (o, prev, curr) -> pivot.setX((double)curr);
|
||||||
|
/**
|
||||||
|
* Single listener for subject position (y) changes
|
||||||
|
*/
|
||||||
|
private ChangeListener<? super Number> pivotY = (o, prev, curr) -> pivot.setY((double)curr);
|
||||||
|
/**
|
||||||
|
* Single listener for subject position (z) changes
|
||||||
|
*/
|
||||||
|
private ChangeListener<? super Number> pivotZ = (o, prev, curr) -> pivot.setZ((double)curr);
|
||||||
|
/**
|
||||||
|
* Distance to switch from third person to bird's eye
|
||||||
|
*/
|
||||||
|
private double THIRD_PERSON_LIMIT = 100;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
|
||||||
|
*/
|
||||||
|
public View3D() {
|
||||||
|
this.world = new Group();
|
||||||
|
this.selectionMap = new HashMap<>();
|
||||||
|
this.target = null;
|
||||||
|
this.scene = new SubScene(world, 300, 300);
|
||||||
|
|
||||||
|
scene.widthProperty().bind(this.widthProperty());
|
||||||
|
scene.heightProperty().bind(this.heightProperty());
|
||||||
|
scene.setFill(new Color(0.2, 0.6, 1, 1));
|
||||||
|
|
||||||
|
scene.setCamera(buildCamera());
|
||||||
|
|
||||||
|
this.getChildren().add(scene);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets up camera view frustum and binds transformations
|
||||||
|
* @return perspective camera
|
||||||
|
*/
|
||||||
|
private PerspectiveCamera buildCamera() {
|
||||||
|
PerspectiveCamera camera = new PerspectiveCamera(true);
|
||||||
|
|
||||||
|
// Set up view frustum
|
||||||
|
nearClip = 0.1;
|
||||||
|
farClip = 3000.0;
|
||||||
|
camera.setNearClip(nearClip);
|
||||||
|
camera.setFarClip(farClip);
|
||||||
|
|
||||||
|
// Set up transformations
|
||||||
|
pivot = new Translate();
|
||||||
|
distance = new Translate();
|
||||||
|
yaw = new Rotate(0, Rotate.Y_AXIS);
|
||||||
|
pitch = new Rotate(0, Rotate.X_AXIS);
|
||||||
|
camera.getTransforms().addAll(pivot, yaw, pitch, distance);
|
||||||
|
|
||||||
|
return camera;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provide the list of subjects to be automatically added or removed from the view as the list
|
||||||
|
* changes.
|
||||||
|
* @param items list managed by client
|
||||||
|
*/
|
||||||
|
public void setItems(ObservableList<Subject3D> items) {
|
||||||
|
this.items = items;
|
||||||
|
this.items.addListener((ListChangeListener<? super Subject3D>) c -> {
|
||||||
|
while(c.next()) {
|
||||||
|
if (c.wasRemoved() || c.wasAdded()) {
|
||||||
|
for (Subject3D shape : c.getRemoved()) {
|
||||||
|
world.getChildren().remove(shape.getMesh());
|
||||||
|
selectionMap.remove(shape.getMesh());
|
||||||
|
}
|
||||||
|
for (Subject3D shape : c.getAddedSubList()) {
|
||||||
|
world.getChildren().add(shape.getMesh());
|
||||||
|
selectionMap.put(shape.getMesh(), shape);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Intercept mouse clicks on subjects in view. The applied listener cannot be removed.
|
||||||
|
*/
|
||||||
|
public void enableTracking() {
|
||||||
|
scene.setOnMousePressed(e -> {
|
||||||
|
PickResult result = e.getPickResult();
|
||||||
|
if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
|
||||||
|
trackSubject(selectionMap.get(result.getIntersectedNode()));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop camera from following the last selected subject
|
||||||
|
*/
|
||||||
|
private void untrackSubject() {
|
||||||
|
if(target != null) {
|
||||||
|
target.getPosition().xProperty().removeListener(pivotX);
|
||||||
|
target.getPosition().yProperty().removeListener(pivotY);
|
||||||
|
target.getPosition().zProperty().removeListener(pivotZ);
|
||||||
|
target.getHeading().angleProperty().removeListener(pivotHeading);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set camera to follow the selected subject
|
||||||
|
* @param subject to track
|
||||||
|
*/
|
||||||
|
private void trackSubject(Subject3D subject) {
|
||||||
|
untrackSubject();
|
||||||
|
target = subject;
|
||||||
|
|
||||||
|
updatePivot(target.getPosition());
|
||||||
|
setYaw(target.getHeading().getAngle());
|
||||||
|
|
||||||
|
target.getPosition().xProperty().addListener(pivotX);
|
||||||
|
target.getPosition().yProperty().addListener(pivotY);
|
||||||
|
target.getPosition().zProperty().addListener(pivotZ);
|
||||||
|
target.getHeading().angleProperty().addListener(pivotHeading);
|
||||||
|
|
||||||
|
this.setDistance(THIRD_PERSON_LIMIT);
|
||||||
|
this.setPitch(20);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNearClip(double nearClip) {
|
||||||
|
this.nearClip = nearClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFarClip(double farClip) {
|
||||||
|
this.farClip = farClip;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the coordinates of the camera pivot once.
|
||||||
|
* @param pivot source of coordinates
|
||||||
|
*/
|
||||||
|
public void updatePivot(Translate pivot) {
|
||||||
|
this.pivot.setX(pivot.getX());
|
||||||
|
this.pivot.setY(pivot.getY());
|
||||||
|
this.pivot.setZ(pivot.getZ());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set distance of camera from pivot
|
||||||
|
* @param distance in units
|
||||||
|
*/
|
||||||
|
public void setDistance(double distance) {
|
||||||
|
this.distance.setZ(-distance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds delta to current distance and changes camera mode if applicable.
|
||||||
|
* Third person limit specifies the distance at which a third person camera
|
||||||
|
* switches to bird's-eye, remaining focused on the same position.
|
||||||
|
* @param delta amount to change distance by
|
||||||
|
*/
|
||||||
|
public void updateDistance(double delta) {
|
||||||
|
double distance = -this.distance.getZ() + delta;
|
||||||
|
|
||||||
|
if(distance <= 0) {
|
||||||
|
this.setDistance(0);
|
||||||
|
} else if(distance > THIRD_PERSON_LIMIT) {
|
||||||
|
untrackSubject();
|
||||||
|
this.setYaw(0);
|
||||||
|
this.setPitch(60);
|
||||||
|
this.setDistance(distance);
|
||||||
|
} else {
|
||||||
|
this.setDistance(distance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set angle of camera from z-axis along ground
|
||||||
|
* @param yaw in degrees
|
||||||
|
*/
|
||||||
|
public void setYaw(double yaw) {
|
||||||
|
this.yaw.setAngle(yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set elevation of camera
|
||||||
|
* @param pitch in degrees
|
||||||
|
*/
|
||||||
|
public void setPitch(double pitch) {
|
||||||
|
this.pitch.setAngle(-pitch);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in new issue