<+>package org.example.newmat;\r\nimport Graph.readFile;\r\nimport Shape.MovingObject;\r\nimport Shape.VisibleWindow;\r\nimport Shape.Obstacle;\r\nimport Shape.ObstacleField;\r\n\r\nimport javafx.animation.AnimationTimer;\r\nimport javafx.application.Application;\r\nimport javafx.geometry.Bounds;\r\nimport javafx.geometry.Point3D;\r\nimport javafx.geometry.Pos;\r\nimport javafx.scene.*;\r\nimport javafx.scene.control.Label;\r\nimport javafx.scene.input.KeyEvent;\r\nimport javafx.scene.layout.StackPane;\r\nimport javafx.scene.paint.Color;\r\nimport javafx.scene.paint.PhongMaterial;\r\nimport javafx.scene.shape.*;\r\nimport javafx.scene.transform.Rotate;\r\nimport javafx.scene.transform.Translate;\r\nimport javafx.stage.Stage;\r\n\r\nimport java.io.IOException;\r\nimport java.util.Arrays;\r\nimport java.util.List;\r\n\r\nimport static java.lang.Math.abs;\r\n\r\n\r\n/**\r\n * Demonstrates advanced JavaFX graphics and window management by creating a 3D scene with\r\n * animated elements and FPS (Frames Per Second) counter.\r\n * <p>\r\n * This application creates a 3D scene containing a textured cube and a dynamically generated\r\n * mesh based on input parameters. It showcases the handling of 3D transformations, animation,\r\n * camera movement, and basic input from the keyboard to manipulate the scene. Additionally,\r\n * it includes a real-time FPS counter overlaid on the scene to monitor performance.\r\n */\r\npublic class GraphicsAndWindowsTest extends Application {\r\n //public static String[] parameters;\r\n public static MovingObject[] objects;\r\n /**\r\n * Starts the JavaFX application.\r\n * This method initializes the 3D scene with its objects, animation timer, and event handlers.\r\n *\r\n * @param stage The primary stage for this application, onto which the scene is set.\r\n * @throws IOException If there is an issue reading the input file for generating the mesh.\r\n */\r\n @Override\r\n public void start(Stage stage) throws IOException {\r\n double windowXOffset = 600;\r\n double windowYOffset = 200;\r\n VisibleWindow sceneWindow = new VisibleWindow(500, -250, -250);\r\n Box ourWindow = sceneWindow.makeWindow();\r\n\r\n\r\n ObstacleField obs = new ObstacleField(sceneWindow.getWindowSize(),sceneWindow.getXOffset(),\r\n sceneWindow.getYOffset(),350,50, 100, 60, \"Box\",\r\n Color.ORANGERED, false);\r\n\r\n String[] ob2 = {\"--image\", \"src/teapot.txt\", \"--speed\", \"32\", \"--dir\", \"4,\", \"7,\", \"10\"};\r\n String[] ob3 = {\"--image\", \"src/teapot.txt\", \"--speed\", \"5\", \"--dir\", \"1,\", \"1,\", \"1\"};\r\n //String[] ob4 = {\"--image\", \"src/teapot.txt\", \"--speed\", \"100\", \"--dir\", \"3,\", \"5,\", \"7\"};\r\n MovingObject newObject1 = new MovingObject(getParameters().getRaw().toArray(new String[0]));\r\n MovingObject newObject2 = new MovingObject(ob2);\r\n MovingObject newObject3 = new MovingObject(ob3);\r\n //MovingObject newObject4 = new MovingObject(ob4);\r\n objects = new MovingObject[3];\r\n objects[0] = newObject1;\r\n objects[1] = newObject2;\r\n objects[2] = newObject3;\r\n //objects[3] = newObject4;\r\n\r\n\r\n Group group3D = new Group(); // Group of our 3D elements\r\n for (MovingObject o : objects ) {\r\n o.setTranslate(sceneWindow.getXOffset(), sceneWindow.getYOffset(),0);\r\n group3D.getChildren().add(o.getMesh());\r\n }\r\n\r\n group3D.setTranslateX(windowXOffset);\r\n group3D.setTranslateY(windowYOffset);\r\n group3D.setTranslateZ(250);\r\n group3D.getChildren().add(ourWindow);\r\n group3D.getChildren().add(new AmbientLight(Color.WHITE));\r\n obs.addToGroup(group3D);\r\n\r\n // NOTE: Due to the perspectiveCamera it looks like it is rotating even though it isn't\r\n // If you comment out this line and the scene.setCamera(camera) line\r\n // You will see it move without the rotation effect\r\n // I think this is pretty cool\r\n Camera camera = new PerspectiveCamera();\r\n camera.setNearClip(.1);\r\n camera.setFarClip(2000);\r\n camera.setTranslateX(-125);\r\n camera.setTranslateY(-250);\r\n camera.setTranslateZ(-250);\r\n\r\n //https://stackoverflow.com/questions/65510979/move-camera-relative-to-view-in-javafx\r\n //Incorporated the code from this post\r\n Point3D xAxis = new Point3D(1,0,0);\r\n Point3D yAxis = new Point3D(0,1,0);\r\n Rotate groupRotX = new Rotate(0, xAxis);\r\n Rotate groupRotY = new Rotate(0, yAxis);\r\n\r\n\r\n group3D.getTransforms().addAll(groupRotY, groupRotX);\r\n\r\n stage.addEventHandler(KeyEvent.KEY_PRESSED, event -> {\r\n switch(event.getCode()){\r\n case LEFT:\r\n groupRotY.setAngle(groupRotY.getAngle() + 10);\r\n break;\r\n case RIGHT:\r\n groupRotY.setAngle(groupRotY.getAngle() - 10);\r\n break;\r\n case UP:\r\n groupRotX.setAngle(groupRotX.getAngle() + 10);\r\n break;\r\n case DOWN:\r\n groupRotX.setAngle(groupRotX.getAngle() - 10);\r\n break;\r\n\r\n }\r\n });\r\n\r\n /*\r\n * SubScene to handle 3D components\r\n * Makes it easier to have 2D and 3D components in the same animation\r\n */\r\n SubScene subScene3D = new SubScene(group3D, 1500, 800);\r\n subScene3D.setFill(Color.SILVER);\r\n subScene3D.setCamera(camera);\r\n\r\n\r\n /*\r\n * Text label to display FPS\r\n */\r\n Label fpsLabel = new Label(\"FPS: 0\");\r\n fpsLabel.setTextFill(Color.WHITE);\r\n\r\n Label scoreLabel = new Label(\"Score: 0\");\r\n fpsLabel.setTextFill(Color.WHITE);\r\n //Lays out the children in Z order with the last member being front most\r\n //Effectively allows you to layer multiple elements\r\n //We are layering our 3D scene with a 2D label on top of it\r\n StackPane Layers = new StackPane();\r\n Layers.getChildren().addAll(subScene3D, fpsLabel, scoreLabel);\r\n StackPane.setAlignment(fpsLabel, Pos.BOTTOM_RIGHT); //Places FPS in bottom right\r\n StackPane.setAlignment(scoreLabel, Pos.TOP_LEFT); //Places FPS in bottom right\r\n\r\n\r\n Scene scene = new Scene(Layers, 1500, 800);\r\n\r\n stage.setTitle(\"FPS test\");\r\n stage.setScene(scene);\r\n stage.show();\r\n\r\n // Animation logic to move the Box back and forth\r\n AnimationTimer timer = new AnimationTimer() {\r\n private long lastUpdate = 0; //Movement timer\r\n private long fpsTimer = 0; //Frame timer\r\n private long frameRate = 0; //Frames per second\r\n private long score = 0; //Frames per second\r\n\r\n\r\n\r\n /**\r\n * Called at every frame while the {@link AnimationTimer} is active to update the scene's elements,\r\n * including object movements, boundary condition checks, mesh deformation on collision, and FPS counter updates.\r\n * <p>\r\n * This method extends the scene update logic with collision detection between the mesh and a virtual bounding box,\r\n * causing mesh deformation upon nearing the box's edges. It calculates new positions for 3D objects based on their\r\n * current positions and direction vectors, simulates movement within the scene, and reverses the movement direction\r\n * at the scene's boundaries. Additionally, it modifies the mesh's vertices to \"squish\" the mesh when it comes close\r\n * to or collides with the bounding box, creating a more dynamic interaction between the scene's objects.\r\n * <p>\r\n * Movement, boundary checking, and mesh deformation logic are applied based on the bounds of the 3D mesh and cube within the scene.\r\n * The FPS counter is updated once every second, based on the number of frames rendered in that interval, to provide real-time\r\n * performance feedback.\r\n *\r\n * @param now The timestamp of the current frame given in nanoseconds. This value is used to ensure smooth animations and\r\n * consistent updates across frames.\r\n */\r\n @Override\r\n public void handle(long now) {\r\n //Built in class that helps calculate the bounds of the mesh for collision\r\n Bounds[] meshBounds = new Bounds[objects.length];\r\n double[][] direction = new double[objects.length][3];\r\n for (int i = 0; i < objects.length; i++) {\r\n meshBounds[i] = objects[i].getMesh().getBoundsInParent();\r\n direction[i] = objects[i].getVelocityVec();\r\n }\r\n //Number is roughly 1 mill divided by 60\r\n if (now - lastUpdate >= 16_000_000) { // Roughly 60 frames per second\r\n int lastChanged = 1;\r\n double winLeftXBound = sceneWindow.getXOffset();\r\n double winTopYBound = sceneWindow.getYOffset();\r\n double WindowSize = sceneWindow.getWindowSize();\r\n // Reverse direction at bounds\r\n for (int i = 0; i < objects.length; i++) {\r\n double GRAVITY = 1;\r\n double AIRRESISTANCE = 0.03;\r\n //Physics updates\r\n //Gravity\r\n direction[i][1] += GRAVITY;\r\n\r\n //air resistance computation\r\n double mag = 0;\r\n for (int j = 0; j < 3; j++ ) {\r\n mag += Math.pow(direction[i][j],2);\r\n }\r\n mag = Math.sqrt(mag);\r\n double airResistance = mag * AIRRESISTANCE;\r\n direction[i][0] = direction[i][0] - (direction[i][0] / mag * airResistance);\r\n direction[i][1] = direction[i][1] - (direction[i][1] / mag * airResistance);\r\n direction[i][2] = direction[i][2] - (direction[i][2] / mag * airResistance);\r\n\r\n\r\n double x = direction[i][0];\r\n double y = direction[i][1];\r\n double z = direction[i][2];\r\n\r\n // the following will be class methods in the future\r\n // Window Collision computation for X\r\n if (meshBounds[i].getMinX() + direction[i][0] < winLeftXBound || meshBounds[i].getMaxX() + direction[i][0] > winLeftXBound + WindowSize) {\r\n\r\n if (meshBounds[i].getMinX() + x < winLeftXBound) {\r\n x = direction[i][0] + (winLeftXBound - (meshBounds[i].getMinX() + x));\r\n direction[i][0] += 1;\r\n } else {\r\n x = direction[i][0] - (meshBounds[i].getMaxX() + direction[i][0] - (winLeftXBound + WindowSize));\r\n }\r\n direction[i][0] *= -1;\r\n lastChanged = 1;\r\n\r\n }\r\n\r\n // Window Collision computation for Y\r\n if (meshBounds[i].getMinY() + direction[i][1] < winTopYBound || meshBounds[i].getMaxY() + direction[i][1] > winTopYBound + WindowSize) {\r\n\r\n if (meshBounds[i].getMinY() + y < winTopYBound) {\r\n y = direction[i][1] + (winTopYBound - (meshBounds[i].getMinY() + y));\r\n direction[i][1] += 1;\r\n } else {\r\n y = direction[i][1] - (meshBounds[i].getMaxY() + direction[i][1] - (winTopYBound + WindowSize));\r\n }\r\n direction[i][1] *= -1;\r\n lastChanged = 2;\r\n }\r\n\r\n // Window Collision computation for Z\r\n if (meshBounds[i].getMinZ() + direction[i][2] < 0 || meshBounds[i].getMaxZ() + direction[i][2] > WindowSize) {\r\n if (meshBounds[i].getMinZ() + z < 0) {\r\n z = direction[i][2] + (0 - (meshBounds[i].getMinZ() + z));\r\n direction[i][2] += 1;\r\n } else {\r\n z = direction[i][2] - (meshBounds[i].getMaxZ() + direction[i][2] - (WindowSize));\r\n }\r\n\r\n direction[i][2] *= -1;\r\n lastChanged = 3;\r\n }\r\n\r\n List<Object> collisionSide = obs.checkCollision(meshBounds[i]);\r\n if(!(collisionSide.get(0).equals(\"NONE\"))){\r\n switch ((String)collisionSide.get(0)){\r\n case \"LEFT\":\r\n direction[i][0] = abs(direction[i][0]);\r\n break;\r\n\r\n case \"RIGHT\":\r\n direction[i][0] = -1 * abs(direction[i][0]);\r\n break;\r\n\r\n case \"TOP\":\r\n direction[i][1] = abs(direction[i][1]);\r\n break;\r\n\r\n case \"BOTTOM\":\r\n direction[i][1] = -1 * abs(direction[i][1]);\r\n break;\r\n\r\n case \"FRONT\":\r\n direction[i][2] = abs(direction[i][2]);\r\n break;\r\n\r\n case \"BACK\":\r\n direction[i][2] = -1 * abs(direction[i][2]);\r\n break;\r\n\r\n default: System.out.println();\r\n }\r\n }\r\n objects[i].updateTranslate(x, y, z);\r\n System.out.println(Arrays.toString(objects[i].getCentroid()));\r\n //objects[i].updateRays();\r\n }\r\n lastUpdate = now;\r\n }\r\n if (fpsTimer == 0) {\r\n fpsTimer = now; // Initialize the FPS timer\r\n }\r\n frameRate++; // Increment frame count for each invocation\r\n //1 mill nanoseconds in a second\r\n if (now - fpsTimer >= 1_000_000_000) { // Every second, update the FPS display\r\n fpsLabel.setText(String.format(\"FPS: %d\", frameRate));\r\n frameRate = 0; // Reset frame count\r\n fpsTimer = now; // Reset the FPS timer\r\n score++;\r\n scoreLabel.setText(String.format(\"Score: %d\", score));\r\n }\r\n\r\n }\r\n };\r\n timer.start();\r\n }\r\n /**\r\n * The entry point of the application. This method is called when the application starts.\r\n *\r\n * @param args Command-line arguments passed to the application. Expected to contain\r\n * parameters for mesh generation and other configurations.\r\n */\r\n public static void main(String[] args) {\r\n Application.launch(args);\r\n }\r\n}\r\n