From 4dcff5c0095548172c85b8504557596effdb3ec5 Mon Sep 17 00:00:00 2001 From: hba56 Date: Tue, 12 Sep 2017 21:55:23 +1200 Subject: [PATCH] SeaSurface files have been added to display the surface in the lobby. Surface is now drawn in the lobby --- .../Controllers/InGameLobbyController.java | 13 +- .../main/java/visualiser/layout/Plane3D.java | 125 ++++++++++++++++ .../java/visualiser/layout/SeaSurface.java | 135 ++++++++++++++++++ .../utils/PerlinNoiseGenerator.java | 88 ++++++++++++ 4 files changed, 356 insertions(+), 5 deletions(-) create mode 100644 racevisionGame/src/main/java/visualiser/layout/Plane3D.java create mode 100644 racevisionGame/src/main/java/visualiser/layout/SeaSurface.java create mode 100644 racevisionGame/src/main/java/visualiser/utils/PerlinNoiseGenerator.java diff --git a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java index 9140d1be..a6442664 100644 --- a/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java +++ b/racevisionGame/src/main/java/visualiser/Controllers/InGameLobbyController.java @@ -9,16 +9,18 @@ import javafx.collections.ObservableList; import javafx.fxml.FXML; import javafx.geometry.Insets; import javafx.scene.Node; -import javafx.scene.control.*; +import javafx.scene.control.Alert; +import javafx.scene.control.ButtonType; +import javafx.scene.control.Label; import javafx.scene.image.ImageView; import javafx.scene.layout.AnchorPane; import javafx.scene.layout.GridPane; -import javafx.scene.shape.Box; import javafx.scene.shape.MeshView; import mock.app.Event; import network.Messages.Enums.RaceStatusEnum; import network.Messages.Enums.RequestToJoinEnum; import visualiser.gameController.ControllerClient; +import visualiser.layout.SeaSurface; import visualiser.layout.Subject3D; import visualiser.layout.View3D; import visualiser.model.VisualiserBoat; @@ -101,10 +103,8 @@ public class InGameLobbyController extends Controller { } private void resetLobby(){ - int count = 0; for (Label label: allPlayerLabels){ - label.setText("Player " + count + 1); - count ++; + label.setText("No Player"); } List nodeCopy = new ArrayList(playerContainer.getChildren()); for (Node node: nodeCopy){ @@ -133,6 +133,9 @@ public class InGameLobbyController extends Controller { playerContainer.add(playerBoatToSet, (count % 3) , row); playerContainer.setMargin(playerBoatToSet, new Insets(10, 10, 10, 10)); + SeaSurface sea = new SeaSurface(750, 200, 250, 0, 210); + subjects.add(sea.getSurface()); + MeshView mesh = new MeshView(importer.getImport()); Subject3D subject = new Subject3D(mesh); subjects.add(subject); diff --git a/racevisionGame/src/main/java/visualiser/layout/Plane3D.java b/racevisionGame/src/main/java/visualiser/layout/Plane3D.java new file mode 100644 index 00000000..0952b61c --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/Plane3D.java @@ -0,0 +1,125 @@ +package visualiser.layout; + +import javafx.scene.shape.TriangleMesh; + +import java.util.ArrayList; +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 pointsList = new ArrayList<>(); + ArrayList 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 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 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 list){ + int[] res = new int[list.size()]; + for (int i = 0; i < list.size(); i++){ + res[i] = list.get(i); + } + return res; + } + + +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/layout/SeaSurface.java b/racevisionGame/src/main/java/visualiser/layout/SeaSurface.java new file mode 100644 index 00000000..fae9a7a9 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/layout/SeaSurface.java @@ -0,0 +1,135 @@ +package visualiser.layout; + +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 visualiser.utils.PerlinNoiseGenerator; + +/** + * Creates a SeaSurface + */ +public class SeaSurface { + private float[][] noiseArray; + private Subject3D surface; + + /** + * 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 + * @param x offset that the sea should be set at position-wise + * @param z offset that the sea should be set at position-wise + */ + public SeaSurface(int size, double freq, double x, double y, double z){ + noiseArray = PerlinNoiseGenerator.createNoise(size, freq); + createSurface(); + surface.setZ(z); + surface.setY(y); + surface.setX(x); + } + + /** + * Creates the sea surface + */ + private void createSurface(){ + Image diffuseMap = createImage(noiseArray.length, noiseArray); + + PhongMaterial material = new PhongMaterial(); + material.setDiffuseMap(diffuseMap); + //material.setSpecularColor(Color.WHITE); + + Plane3D seaPlane = new Plane3D(noiseArray.length, noiseArray.length, 10, 10); + MeshView seaSurface = new MeshView(seaPlane); +// Box seaSurface = new Box(noiseArray.length, 0.1, noiseArray.length); + seaSurface.setMaterial(material); + seaSurface.setMouseTransparent(true); + seaSurface.toFront(); + //seaSurface.setRotationAxis(new Point3D(1, 0, 0)); + //seaSurface.setRotate(90); + + surface = new Subject3D(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 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; + } + + /** + * Get surface + * @return the surface so it can be drawn + */ + public Subject3D getSurface(){ + return surface; + } + +} \ No newline at end of file diff --git a/racevisionGame/src/main/java/visualiser/utils/PerlinNoiseGenerator.java b/racevisionGame/src/main/java/visualiser/utils/PerlinNoiseGenerator.java new file mode 100644 index 00000000..becf59a2 --- /dev/null +++ b/racevisionGame/src/main/java/visualiser/utils/PerlinNoiseGenerator.java @@ -0,0 +1,88 @@ +package visualiser.utils; + +/** + * Perlin Noise Generator + */ +public class PerlinNoiseGenerator { + + + /** + * Create an array of the given size with values of perlin noise + * @param size size of array that you wish to create + * @param freq frequency that the noise is to be generated at. + * @return noise generated + */ + public static float[][] createNoise( int size, double freq) { + float[][] noiseArray = new float[(int) size][(int) size]; + + for (int x = 0; x < size; x++) { + for (int y = 0; y < size; y++) { + + double frequency = freq / (double) size; + + double noise = ImprovedNoise.noise(x * frequency, y * frequency, 0); + + noiseArray[x][y] = (float) noise; + } + } + + return noiseArray; + + } + + /** + * Perlin noise generator + * + * // JAVA REFERENCE IMPLEMENTATION OF IMPROVED NOISE - COPYRIGHT 2002 KEN PERLIN. + * // http://mrl.nyu.edu/~perlin/paper445.pdf + * // http://mrl.nyu.edu/~perlin/noise/ + */ + public final static class ImprovedNoise { + static public double noise(double x, double y, double z) { + int X = (int)Math.floor(x) & 255, // FIND UNIT CUBE THAT + Y = (int)Math.floor(y) & 255, // CONTAINS POINT. + Z = (int)Math.floor(z) & 255; + x -= Math.floor(x); // FIND RELATIVE X,Y,Z + y -= Math.floor(y); // OF POINT IN CUBE. + z -= Math.floor(z); + double u = fade(x), // COMPUTE FADE CURVES + v = fade(y), // FOR EACH OF X,Y,Z. + w = fade(z); + int A = p[X ]+Y, AA = p[A]+Z, AB = p[A+1]+Z, // HASH COORDINATES OF + B = p[X+1]+Y, BA = p[B]+Z, BB = p[B+1]+Z; // THE 8 CUBE CORNERS, + + return lerp(w, lerp(v, lerp(u, grad(p[AA ], x , y , z ), // AND ADD + grad(p[BA ], x-1, y , z )), // BLENDED + lerp(u, grad(p[AB ], x , y-1, z ), // RESULTS + grad(p[BB ], x-1, y-1, z ))),// FROM 8 + lerp(v, lerp(u, grad(p[AA+1], x , y , z-1 ), // CORNERS + grad(p[BA+1], x-1, y , z-1 )), // OF CUBE + lerp(u, grad(p[AB+1], x , y-1, z-1 ), + grad(p[BB+1], x-1, y-1, z-1 )))); + } + static double fade(double t) { return t * t * t * (t * (t * 6 - 15) + 10); } + static double lerp(double t, double a, double b) { return a + t * (b - a); } + static double grad(int hash, double x, double y, double z) { + int h = hash & 15; // CONVERT LO 4 BITS OF HASH CODE + double u = h<8 ? x : y, // INTO 12 GRADIENT DIRECTIONS. + v = h<4 ? y : h==12||h==14 ? x : z; + return ((h&1) == 0 ? u : -u) + ((h&2) == 0 ? v : -v); + } + static final int p[] = new int[512], permutation[] = { 151,160,137,91,90,15, + 131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,23, + 190, 6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33, + 88,237,149,56,87,174,20,125,136,171,168, 68,175,74,165,71,134,139,48,27,166, + 77,146,158,231,83,111,229,122,60,211,133,230,220,105,92,41,55,46,245,40,244, + 102,143,54, 65,25,63,161, 1,216,80,73,209,76,132,187,208, 89,18,169,200,196, + 135,130,116,188,159,86,164,100,109,198,173,186, 3,64,52,217,226,250,124,123, + 5,202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42, + 223,183,170,213,119,248,152, 2,44,154,163, 70,221,153,101,155,167, 43,172,9, + 129,22,39,253, 19,98,108,110,79,113,224,232,178,185, 112,104,218,246,97,228, + 251,34,242,193,238,210,144,12,191,179,162,241, 81,51,145,235,249,14,239,107, + 49,192,214, 31,181,199,106,157,184, 84,204,176,115,121,50,45,127, 4,150,254, + 138,236,205,93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180 + }; + static { for (int i=0; i < 256 ; i++) p[256+i] = p[i] = permutation[i]; } + } + +} \ No newline at end of file