Fixed merge-related bugs

This commit is contained in:
Connor Taylor-Brown
2017-09-13 13:12:26 +12:00
24 changed files with 802 additions and 102 deletions
@@ -0,0 +1,89 @@
package visualiser.layout;
import com.sun.corba.se.impl.orbutil.graph.Graph;
import javafx.scene.shape.Box;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Rectangle;
import javafx.scene.shape.Sphere;
import shared.model.GPSCoordinate;
import visualiser.model.GraphCoordinate;
import visualiser.utils.GPSConverter;
import java.util.ArrayList;
import java.util.List;
/**
* Class that creates a 3d boundary based on gps coordinates
*/
public class Boundary3D {
public static double thickness = 1;
private List<Subject3D> boundaryNodes = new ArrayList<>();
private List<Subject3D> boundaryConnectors = new ArrayList<>();
private GPSConverter gpsConverter;
public Boundary3D(List<GPSCoordinate> points, GPSConverter gpsConverter){
this.gpsConverter = gpsConverter;
setBoundaryNodes(points);
}
/**
* Splits up the list so that it generates a edge of the boundary
* @param points boundary gpscoordinate
*/
private void setBoundaryNodes(List<GPSCoordinate> points){
if (points.size() < 2){
return;
}
System.out.println(points.size());
for (int i = 0; i < points.size(); i++){
if (i + 1 != points.size()){
addBound(points.get(i), points.get(i + 1));
} else {
addBound(points.get(i), points.get(0));
}
}
}
/**
* Add a two point boundary this will create a sphere at coord1 and a line to coord 2
* this is to reduce double up (2 spheres in one point).
* @param coord1 point to make sphere and start the line.
* @param coord2 point to end the line.
*/
private void addBound(GPSCoordinate coord1, GPSCoordinate coord2){
GraphCoordinate graphCoord1 = gpsConverter.convertGPS(coord1);
GraphCoordinate graphCoord2 = gpsConverter.convertGPS(coord2);
GraphCoordinate avgCoord = new GraphCoordinate((graphCoord1.getX() + graphCoord2.getX()) / 2,
(graphCoord1.getY() + graphCoord2.getY()) / 2);
double a = (graphCoord1.getX() - graphCoord2.getX());
double b = (graphCoord1.getY() - graphCoord2.getY());
double c = Math.sqrt(a * a + b * b);
Subject3D bound1 = new Subject3D(new Sphere(thickness * 2));
bound1.setX(graphCoord1.getX());
bound1.setZ(graphCoord1.getY());
boundaryNodes.add(bound1);
Subject3D connector = new Subject3D(new Box(c, thickness, thickness));
connector.setX(avgCoord.getX());
connector.setZ(avgCoord.getY());
double angle = 90 + Math.toDegrees(GPSConverter.getAngle(graphCoord2, graphCoord1));
connector.setHeading(angle);
boundaryConnectors.add(connector);
}
/**
* get the 3d objects to draw
* @return 3d boundary to draw
*/
public List<Subject3D> getBoundaryNodes(){
//these two must be concatenated with nodes after connectors else the spheres will not overlap the lines
ArrayList<Subject3D> result = new ArrayList<>(boundaryConnectors);
result.addAll(boundaryNodes);
return result;
}
}
@@ -0,0 +1,130 @@
package visualiser.layout;
import com.sun.javafx.geom.PickRay;
import com.sun.javafx.scene.input.PickResultChooser;
import com.sun.javafx.sg.prism.NGNode;
import javafx.scene.Node;
import javafx.scene.shape.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* 3D plane
*/
public class Plane3D extends TriangleMesh{
/**
* Length is up down, and width is left right. Drawn on the x-y plane with z kept at 0.
* @param width width of the plane
* @param length length of the plane
* @param subdivisionsWidth number of divisions along the width of the plane
* @param subdivisionsLength number of division along the length of the plane
*/
public Plane3D(float width, float length, int subdivisionsWidth, int subdivisionsLength){
//add texture points and vertex points
float subWidth = width / (float) subdivisionsWidth;
float subLength = length / (float) subdivisionsLength;
ArrayList<Float> pointsList = new ArrayList<>();
ArrayList<Float> textureCoord = new ArrayList<>();
float startW = -width/2;
float startL = -length/2;
for (float l = 0; l <= length; l += subLength) {
for (float w = 0; w <= width; w += subWidth){
//add points
pointsList.add(w + startW);
pointsList.add(l + startL);
pointsList.add(0f);
//addTexture coords
textureCoord.add(1 - w/width);
textureCoord.add(1 - l/length);
}
}
this.getPoints().setAll(copyListToArray(pointsList));
this.getTexCoords().setAll(copyListToArray(textureCoord));
//connect points to make faces
ArrayList<Integer> faces = new ArrayList<>();
int listSize = pointsList.size()/3;
int divsInRow = subdivisionsWidth + 1;
for (int i = 0; i < listSize; i++){
int row = i/divsInRow;
if (row < 1){
continue;
}
boolean notFirstCol = (i) % divsInRow != 0;
boolean notLastCol = (i + 1) % divsInRow != 0;
if (notFirstCol){
faces.add(i);
faces.add(i);
// printPointAtIndex(i);
faces.add(i - divsInRow);
faces.add(i - divsInRow);
// printPointAtIndex(i - divsInRow);
faces.add(i - 1);
faces.add(i - 1);
// printPointAtIndex(i-1);
}
if (notLastCol) {
faces.add(i - divsInRow + 1);
faces.add(i - divsInRow + 1);
// printPointAtIndex(i - divsInRow + 1);
faces.add(i - divsInRow);
faces.add(i - divsInRow);
// printPointAtIndex(i - divsInRow);
faces.add(i);
faces.add(i);
// printPointAtIndex(i);
}
}
this.getFaces().setAll(copyListToIntArray(faces));
}
/**
* Testing function to see if the points are correct
* @param index index that the points correspond to (remember 3 is a point)
*/
private void printPointAtIndex(int index){
int i = index * 3;
float x = this.getPoints().get(i);
float y = this.getPoints().get(i + 1);
float z = this.getPoints().get(i + 2);
System.out.println(String.format("Point at %d is x:%f, y:%f, z:%f", index, x, y, z));
}
/**
* copies the list to a float array because java List.toArray isn't working
* @param list list to copy
* @return array
*/
private static float[] copyListToArray(List<Float> list){
float[] res = new float[list.size()];
for (int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
/**
* copies the list to an integer array because java List.toArray isn't working
* @param list list to copy
* @return array
*/
private static int[] copyListToIntArray(List<Integer> list){
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++){
res[i] = list.get(i);
}
return res;
}
}
@@ -0,0 +1,126 @@
package visualiser.layout;
import javafx.geometry.Point3D;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.Box;
import javafx.scene.shape.MeshView;
import javafx.scene.shape.Shape3D;
import javafx.scene.shape.TriangleMesh;
import visualiser.utils.PerlinNoiseGenerator;
/**
* Creates a SeaSurface
*/
public class SeaSurface extends Subject3D {
/**
* Sea Surface Constructor
* @param size size of the sea surface (has to be square for simplicity's sake)
* @param freq frequency the perlin noise is to be generated at
*/
public SeaSurface(int size, double freq){
super(createSurface(size, freq));
}
/**
* Creates the sea surface
*/
private static Shape3D createSurface(int size, double freq){
float[][] noiseArray = PerlinNoiseGenerator.createNoise(size, freq);
Image diffuseMap = createImage(noiseArray.length, noiseArray);
PhongMaterial material = new PhongMaterial();
material.setDiffuseMap(diffuseMap);
Plane3D seaPlane = new Plane3D(noiseArray.length, noiseArray.length, 10, 10);
MeshView seaSurface = new MeshView(seaPlane);
seaSurface.setMaterial(material);
seaSurface.setMouseTransparent(true);
seaSurface.toFront();
return seaSurface;
}
/**
* Create texture for uv mapping
* @param size size of the image to make
* @param noise array of noise
* @return image that is created
*/
private static Image createImage(double size, float[][] noise) {
int width = (int) size;
int height = (int) size;
WritableImage wr = new WritableImage(width, height);
PixelWriter pw = wr.getPixelWriter();
//interpolate colours based on noise
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
float value = noise[x][y];
double gray = normalizeValue(value, -.5, .5, 0., 1.);
gray = clamp(gray, 0, 1);
//values to interpolate on
Color brightBlue = new Color(0.06, 0.5, .78, 1);
Color lightBlue = new Color(0.15, 0.68, .88, 1);
Color lighterBlue = new Color(0.28, 0.73, .91, 1);
Color colour = Color.WHITE.interpolate(brightBlue, gray).interpolate(lighterBlue, gray).interpolate(lightBlue, gray);
pw.setColor(x, y, colour);
}
}
return wr;
}
/**
* Nomalises the values so that the colours are correct
* @param value value to normalise
* @param min current min
* @param max current max
* @param newMin new min
* @param newMax new max
* @return returns normalised value
*/
private static double normalizeValue(double value, double min, double max, double newMin, double newMax) {
return (value - min) * (newMax - newMin) / (max - min) + newMin;
}
/**
* clamps a value between a min and max
* @param value value to clamp
* @param min minimum value it can be
* @param max maximum value it can be
* @return result after clamp
*/
private static double clamp(double value, double min, double max) {
if (Double.compare(value, min) < 0)
return min;
if (Double.compare(value, max) > 0)
return max;
return value;
}
/**
* Prevent rescaling of sea surface
* @param scale ignored
*/
@Override
public void setScale(double scale) {}
}
@@ -0,0 +1,162 @@
package visualiser.layout;
import javafx.geometry.Point3D;
import javafx.scene.image.Image;
import javafx.scene.image.PixelWriter;
import javafx.scene.image.WritableImage;
import javafx.scene.paint.Color;
import javafx.scene.paint.PhongMaterial;
import javafx.scene.shape.MeshView;
import javafx.scene.transform.Rotate;
import visualiser.utils.PerlinNoiseGenerator;
import java.util.ArrayList;
import java.util.List;
/**
* Creates a skyBox
*/
public class SkyBox {
private int size;
private double x;
private double y;
private double z;
private double freq;
private List<Subject3D> skyBoxPlanes = new ArrayList<>();
public SkyBox(int edgeLength, double freq, double x, double y, double z) {
this.size = edgeLength;
this.x = x;
this.y = y;
this.z = z;
this.freq = freq;
makeSkyBox();
}
private void makeSkyBox() {
addTop();
addFront();
addBack();
addLeft();
addRight();
addSeaOverlay();
}
private void addTop() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/skyTop.png")));
surface.setRotationAxis(new Point3D(0, 0, 1));
surface.setRotate(180);
surface.setTranslateX(x);
surface.setTranslateY(y - size + 1);
surface.setTranslateZ(z);
Subject3D top = new Subject3D(surface);
skyBoxPlanes.add(top);
}
private void addRight() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/skyRight.png")));
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setTranslateX(x);
surface.setTranslateY(y - size/2);
surface.setTranslateZ(z + size/2 - 1);
Subject3D right = new Subject3D(surface);
skyBoxPlanes.add(right);
}
private void addLeft() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/skyLeft.png")));
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(-90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setScaleX(-1);
surface.setScaleZ(-1);
surface.setTranslateX(x);
surface.setTranslateY(y - size/2);
surface.setTranslateZ(z - size/2 + 1);
Subject3D left = new Subject3D(surface);
skyBoxPlanes.add(left);
}
private void addBack() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/skyBack.png")));
surface.getTransforms().add(new Rotate(90, 0, 0));
surface.setRotationAxis(new Point3D(1, 0, 0));
surface.setRotate(-90);
surface.setScaleY(-1);
surface.setScaleZ(-1);
surface.setTranslateX(x - size/2 + 1);
surface.setTranslateY(y - size/2);
surface.setTranslateZ(z);
Subject3D back = new Subject3D(surface);
skyBoxPlanes.add(back);
}
private void addFront() {
MeshView surface = makeSurface(new Image(getClass().getClassLoader().getResourceAsStream("images/skybox/skyFront.png")));
surface.setTranslateX(size/2);
surface.setTranslateY(size/2);
surface.setRotationAxis(new Point3D(0, 0, 1));
surface.setRotate(-90);
surface.setTranslateX(-size/2);
surface.setTranslateY(-size/2);
surface.setTranslateX(x + size/2 - 1);
surface.setTranslateY(y - size/2);
surface.setTranslateZ(z);
Subject3D front = new Subject3D(surface);
skyBoxPlanes.add(front);
}
private MeshView makeSurface(Image diffuseMap) {
PhongMaterial material = new PhongMaterial();
//material.setDiffuseColor(Color.BLUE);
material.setDiffuseMap(diffuseMap);
//material.setSpecularColor(Color.WHITE);
Plane3D plane = new Plane3D(size, size, 10, 10);
MeshView surface = new MeshView(plane);
surface.setMaterial(material);
surface.setMouseTransparent(true);
return surface;
}
private void addSeaOverlay() {
SeaSurface seaSurface = new SeaSurface(size * 3, freq);
seaSurface.setX(x);
seaSurface.setY(y - size/4 + 1);
seaSurface.setZ(z);
skyBoxPlanes.add(seaSurface);
}
public List<Subject3D> getSkyBoxPlanes() {
return skyBoxPlanes;
}
}
@@ -2,6 +2,7 @@ package visualiser.layout;
import javafx.scene.shape.Shape3D;
import javafx.scene.transform.Rotate;
import javafx.scene.transform.Scale;
import javafx.scene.transform.Translate;
/**
@@ -27,6 +28,8 @@ public class Subject3D {
*/
private Rotate heading;
private Scale scale;
/**
* Constructor for view subject wrapper
* @param mesh to be rendered
@@ -34,10 +37,11 @@ public class Subject3D {
public Subject3D(Shape3D mesh, int sourceID) {
this.mesh = mesh;
this.sourceID = sourceID;
this.scale = new Scale();
this.position = new Translate();
this.heading = new Rotate(0, Rotate.Y_AXIS);
this.mesh.getTransforms().addAll(position, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
this.mesh.getTransforms().addAll(position, scale, heading, new Rotate(90, Rotate.X_AXIS), new Rotate(180, Rotate.Y_AXIS));
}
public Shape3D getMesh() {
@@ -56,6 +60,12 @@ public class Subject3D {
return heading;
}
public void setScale(double scale) {
this.scale.setX(scale);
this.scale.setY(scale);
this.scale.setZ(scale);
}
public void setX(double x) {
position.setX(x);
}
@@ -3,9 +3,7 @@ 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.*;
import javafx.scene.input.PickResult;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
@@ -89,12 +87,17 @@ public class View3D extends Pane {
/**
* Distance to switch from third person to bird's eye
*/
private double THIRD_PERSON_LIMIT = 100;
private final double THIRD_PERSON_LIMIT = 100;
/**
* Distance to stop zoom
*/
private final double FIRST_PERSON_LIMIT = 2;
/**
* Default constructor for View3D. Sets up Scene and PerspectiveCamera.
* @param fill whether or not to fill the background of the view.
*/
public View3D() {
public View3D(boolean fill) {
this.world = new Group();
this.shapeMap = new HashMap<>();
this.sourceMap = new HashMap<>();
@@ -103,13 +106,18 @@ public class View3D extends Pane {
scene.widthProperty().bind(this.widthProperty());
scene.heightProperty().bind(this.heightProperty());
scene.setFill(new Color(0.2, 0.6, 1, 1));
if (fill) {
scene.setFill(new Color(0.2, 0.6, 1, 1));
}
scene.setCamera(buildCamera());
this.getChildren().add(scene);
}
public View3D(){
this(true);
}
/**
* Sets up camera view frustum and binds transformations
* @return perspective camera
@@ -169,11 +177,37 @@ public class View3D extends Pane {
scene.setOnMousePressed(e -> {
PickResult result = e.getPickResult();
if(result != null && result.getIntersectedNode() != null && result.getIntersectedNode() instanceof Shape3D) {
untrackSubject();
trackSubject(shapeMap.get(result.getIntersectedNode()));
setThirdPerson();
}
});
}
/**
* Configures camera to third person view
*/
public void setThirdPerson() {
this.setDistance(THIRD_PERSON_LIMIT / 2);
this.setPitch(10);
for(Subject3D item: items) {
item.setScale(0.1);
}
}
/**
* Configures camera to bird's eye view
*/
public void setBirdsEye() {
this.setYaw(0);
this.setPitch(60);
for(Subject3D item: items) {
item.setScale(1);
}
}
/**
* Stop camera from following the last selected subject
*/
@@ -191,7 +225,6 @@ public class View3D extends Pane {
* @param subject to track
*/
private void trackSubject(Subject3D subject) {
untrackSubject();
target = subject;
updatePivot(target.getPosition());
@@ -201,9 +234,6 @@ public class View3D extends Pane {
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) {
@@ -241,13 +271,12 @@ public class View3D extends Pane {
public void updateDistance(double delta) {
double distance = -this.distance.getZ() + delta;
if(distance <= 0) {
this.setDistance(0);
if(distance <= FIRST_PERSON_LIMIT) {
this.setDistance(FIRST_PERSON_LIMIT);
} else if(distance > THIRD_PERSON_LIMIT) {
untrackSubject();
this.setYaw(0);
this.setPitch(60);
this.setDistance(distance);
untrackSubject();
setBirdsEye();
} else {
this.setDistance(distance);
}
@@ -268,4 +297,12 @@ public class View3D extends Pane {
public void setPitch(double pitch) {
this.pitch.setAngle(-pitch);
}
public void addAmbientLight(AmbientLight ambientLight) {
this.world.getChildren().add(ambientLight);
}
public void addPointLight(PointLight pointLight) {
this.world.getChildren().add(pointLight);
}
}