Added example project doc

This commit is contained in:
2021-06-09 22:36:28 +12:00
commit f3d2740a25
18 changed files with 970 additions and 0 deletions
@@ -0,0 +1,129 @@
package nz.ac.massey.javaecs.examples;
import java.util.Random;
import nz.ac.massey.javaecs.*;
import nz.ac.massey.javaecs.examples.Components.*;
import nz.ac.massey.javaecs.examples.Systems.*;
/**
* Physics simulation app
*/
public final class App {
private App() {
}
/**
* Simple physics simulation.
* @param args The arguments of the program.
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
Engine gameEngine = new Engine(16384);
gameEngine.registerComponent(Gravity.class);
gameEngine.registerComponent(RidgidBody.class);
gameEngine.registerComponent(Vec2D.class);
gameEngine.registerComponent(Collider.class);
gameEngine.registerComponent(LogUpdate.class);
gameEngine.registerComponent(Render.class);
gameEngine.registerComponent(BoxRender.class);
PhysicsSystem physicsSystem = new PhysicsSystem(gameEngine);
gameEngine.registerSystem(PhysicsSystem.class, physicsSystem);
CollisionSystem collisionSystem = new CollisionSystem(gameEngine);
gameEngine.registerSystem(CollisionSystem.class, collisionSystem);
LogUpdateSystem logUpdateSystem = new LogUpdateSystem(gameEngine);
gameEngine.registerSystem(LogUpdateSystem.class, logUpdateSystem);
RenderSystem renderSystem = new RenderSystem(gameEngine, 10.);
gameEngine.registerSystem(RenderSystem.class, renderSystem);
// Define a play area 100 * 100, make sure to use worldspace not screenspace (top left is 0, 100, not 0,0)
// Bottom boundary
Entity newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., -50.,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
gameEngine.addComponent(newEnt, Collider.class, new Collider(200., 50.)); // 200 along, 50 up
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(200, 50, 64, 64, 64));
// Left boundary
newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., -50.,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
gameEngine.addComponent(newEnt, Collider.class, new Collider(50., 200.));
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(50, 200, 64, 64, 64));
// Top boundary
newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(-50., 100.,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
gameEngine.addComponent(newEnt, Collider.class, new Collider(200., 50.));
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(200, 50, 64, 64, 64));
// Right boundary
newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(100., -50.,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(0., 0., 0., 0., 0.f, 0., -1.));
gameEngine.addComponent(newEnt, Collider.class, new Collider(50., 200.));
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(50, 200, 64, 64, 64));
// One object
newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(62., 90.,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(1., 0., 0., 0., 0.f, 0., 1.));
gameEngine.addComponent(newEnt, Collider.class, new Collider(10., 10.));
gameEngine.addComponent(newEnt, Gravity.class, new Gravity(0, -9.80665, 45., 45.)); // Terminal velocity is 45 m/s
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(10, 10, 255, 64, 64));
// Other objects
Random rand = new Random();
for (int i = 0; i < 100; i++) {
newEnt = gameEngine.createEntity();
gameEngine.addComponent(newEnt, Vec2D.class, new Vec2D(10 + rand.nextDouble()* 80, 10+ rand.nextDouble()* 80,0));
gameEngine.addComponent(newEnt, RidgidBody.class, new RidgidBody(-2.5 + rand.nextDouble()* 5., 0., 0., 0., 0.f, 0., rand.nextDouble() * 20.));
double size = 3 + rand.nextDouble() * 7;
gameEngine.addComponent(newEnt, Collider.class, new Collider(size, size));
gameEngine.addComponent(newEnt, Gravity.class, new Gravity(0, -9.80665, 45., 45.)); // Terminal velocity is 45 m/s
gameEngine.addComponent(newEnt, LogUpdate.class, null); // Add to object to print pos of
gameEngine.addComponent(newEnt, Render.class, new Render(BoxRender.class));
gameEngine.addComponent(newEnt, BoxRender.class, new BoxRender(size, size, 30 + rand.nextInt(225), 30 + rand.nextInt(225), 30 + rand.nextInt(225)));
}
//gameEngine.addComponent(newEnt, LogUpdate.class, null);
physicsSystem.init();
collisionSystem.init();
logUpdateSystem.init();
renderSystem.init();
double dt = 0.;
double frameRate = 1.0 / 144; // 1 second divided by target number of frames
double idleTime = 0.0; // the amount of time spent sleeping
boolean exit = false;
long loopStart = System.currentTimeMillis();
long startTime = System.nanoTime();
while (!exit){
collisionSystem.update();
physicsSystem.update(dt);
//logUpdateSystem.update();
// The render system should be uncoupled from the physics and collision
renderSystem.update();
dt = (System.nanoTime() - startTime) / 1e9; // convert nanoseconds to seconds
startTime = System.nanoTime();
if (dt < frameRate){
idleTime = frameRate-dt;
Thread.sleep((int)((frameRate-dt)*1000));
dt = (System.nanoTime() - startTime) / 1e9;
startTime = System.nanoTime();
}
}
}
}
@@ -0,0 +1,19 @@
package nz.ac.massey.javaecs.examples.Components;
public class BoxRender {
public double r = 255;
public double g = 255;
public double b = 255;
public double xSize = 0.0;
public double ySize = 0.0;
public BoxRender(double xSize, double ySize, int red, int green, int blue){
this.xSize = xSize;
this.ySize = ySize;
r = red/255.;
g = green/255.;
b = blue/255.;
}
}
@@ -0,0 +1,15 @@
package nz.ac.massey.javaecs.examples.Components;
public class Collider {
public Collider(double x, double y)
{
this.x = x;
this.y = y;
}
// bottom-right, top-right is specified by position
public double x = 0.0;
public double y = 0.0;
public boolean collided = false;
public double travelDirection = 0.0;
}
@@ -0,0 +1,22 @@
package nz.ac.massey.javaecs.examples.Components;
public class Gravity {
public Gravity(){
}
public Gravity(double x, double y){
this.x = x;
this.y = y;
}
public Gravity(double x, double y, double terminalX, double terminalY){
this.x = x;
this.y = y;
this.terminalX = terminalX;
this.terminalY = terminalY;
}
public double x = 0.0;
public double y = -9.80665; // Force of gravity (from https://www.bipm.org/en/publications/si-brochure - 9th edition - p159 - Bureau International des Poids et Mesures - CC BY 4.0)
// Acceleration due to gravity won't be considered if the speed exceeds this. i.e. Negative values are ignored (unlimited)
public double terminalX = -1.0;
public double terminalY = -1.0;
}
@@ -0,0 +1,5 @@
package nz.ac.massey.javaecs.examples.Components;
public class LogUpdate {
}
@@ -0,0 +1,11 @@
package nz.ac.massey.javaecs.examples.Components;
import java.lang.reflect.Type;
public class Render {
public Type renderType;
public Render(Type t){
renderType = t;
}
}
@@ -0,0 +1,26 @@
package nz.ac.massey.javaecs.examples.Components;
public class RidgidBody {
public RidgidBody(){}
public RidgidBody(double xVel, double yVel, double xAcc, double yAcc, float compressability, double bounciness, double mass){
this.xVel = xVel;
this.yVel = yVel;
this.xAcc = xAcc;
this.yAcc = yAcc;
this.compressability = compressability;
this.bounciness = bounciness;
this.mass = mass;
}
// Velocities
public double xVel = 0.0;
public double yVel = 0.0;
// accelerations
public double xAcc = 0.0;
public double yAcc = 0.0;
// physical properties
public float compressability = 0.01f; // How much the object can squish the other
public double bounciness = 0.75; // How bouncy the object is
public double mass = 1.0;
}
@@ -0,0 +1,13 @@
package nz.ac.massey.javaecs.examples.Components;
public class Vec2D {
public Vec2D(double x, double y, int z){
this.x = x;
this.y = y;
this.z = z;
}
public double x = 0.0;
public double y = 0.0;
public int z = 0;
}
@@ -0,0 +1,252 @@
package nz.ac.massey.javaecs.examples.Systems;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.stream.Stream;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Collider;
import nz.ac.massey.javaecs.examples.Components.Gravity;
import nz.ac.massey.javaecs.examples.Components.RidgidBody;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
public class CollisionSystem extends ECSSystem {
Engine gameEngine;
public CollisionSystem(Engine gameEngine) {
this.gameEngine = gameEngine;
// Set registrations
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Vec2D.class));
registrationSet.set(gameEngine.getComponentIndex(Collider.class));
registrationSet.set(gameEngine.getComponentIndex(RidgidBody.class));
}
@Override
public void init() {
// TODO Auto-generated method stub
}
public void updateNew() {
Set<Entity> processed = new HashSet<>();
// Gets all items we have collided with
for (Entity entity : entities) {
RidgidBody ridgidBody = (RidgidBody) gameEngine.getComponentData(entity, RidgidBody.class);
// Only considering elements with mass just now
if (ridgidBody.mass > 0){
Vec2D pos = (Vec2D) gameEngine.getComponentData(entity, Vec2D.class);
Collider collider = (Collider) gameEngine.getComponentData(entity, Collider.class);
// The pos of each bounding line of the entity
// i.e.
//
// -------------
// |###########|
// |###########|
// |###########|
// |###########|
// -------------
//
double eleft = pos.x;
double ebottom = pos.y;
double eright = pos.x + collider.x;
double etop = pos.y + collider.y;
double evectorAngle = calcAngle(ridgidBody.xVel, ridgidBody.yVel);
if (ridgidBody.mass > 0){
// Dont't recalc for entities that aren't static
processed.add(entity);
}
for (Entity entity2 : entities) {
if (!processed.contains(entity2)) {
// skip entities that have already been processed
Vec2D oPos = (Vec2D) gameEngine.getComponentData(entity2, Vec2D.class);
RidgidBody oRidgidBody = (RidgidBody) gameEngine.getComponentData(entity2, RidgidBody.class);
Collider oCollider = (Collider) gameEngine.getComponentData(entity2, Collider.class);
double oleft = oPos.x;
double obottom = oPos.y;
double oright = oPos.x + oCollider.x;
double otop = oPos.y + oCollider.y;
// Check if a collision occured
// Few conditions required, that is,
// - oS2 must be greater than eS0, else they aren't close
// - eS2 must be greater than oS0, for the same reason
// - either
if (!(eleft > oright || oleft > eright || etop < obottom || otop < ebottom)){
// Collided
// Grab the angle the other is moving, to figure the quadrant to move to
double ovectorAngle = calcAngle(oRidgidBody.xVel, oRidgidBody.yVel);
// Grab the vector sums
double xVelSum = ridgidBody.xVel + oRidgidBody.xVel;
double yVelSum = ridgidBody.yVel + oRidgidBody.yVel;
// Collision occurred
}
}
}
}
}
}
@Override
public void update() {
// Parallelising this loop ()
Set<Entity> processed = new HashSet<>();
// Gets all items we have collided with
for (Entity entity : entities) {
Vec2D pos = (Vec2D) gameEngine.getComponentData(entity, Vec2D.class);
RidgidBody ridgidBody = (RidgidBody) gameEngine.getComponentData(entity, RidgidBody.class);
Collider collider = (Collider) gameEngine.getComponentData(entity, Collider.class);
if (ridgidBody.mass > 0){
// Only ignore non-static colliders
processed.add(entity);
}
for (Entity entity2 : entities) {
if (!processed.contains(entity2)) {
// skip entities that have already been processed
Vec2D otherPos = (Vec2D) gameEngine.getComponentData(entity2, Vec2D.class);
RidgidBody otherRidgidBody = (RidgidBody) gameEngine.getComponentData(entity2, RidgidBody.class);
Collider otherCollider = (Collider) gameEngine.getComponentData(entity2, Collider.class);
if (ridgidBody.mass >= 0 || otherRidgidBody.mass >= 0) {
// Evaluate a collision in the top-left
boolean tlCollide = pos.x > otherPos.x && pos.x < otherPos.x + otherCollider.x
&& pos.y > otherPos.y && pos.y < otherPos.y + otherCollider.y;
// Evaluate a collision in the bottom-left
boolean blCollide = pos.x > otherPos.x && pos.x < otherPos.x + otherCollider.x
&& pos.y + collider.y > otherPos.y && pos.y + collider.y < otherPos.y + otherCollider.y;
// Evaluate a collision in the bottom-right
boolean brCollide = pos.x + collider.x > otherPos.x
&& pos.x + collider.x < otherPos.x + otherCollider.x && pos.y + collider.y > otherPos.y
&& pos.y + collider.y < otherPos.y + otherCollider.y;
// Evaluate a collision in the top-right
boolean trCollide = pos.x + collider.x > otherPos.x
&& pos.x + collider.x < otherPos.x + otherCollider.x && pos.y > otherPos.y
&& pos.y < otherPos.y + otherCollider.y;
if (tlCollide || blCollide || brCollide || trCollide) {
// Determine collision nature
if (tlCollide) {
int k = 0;
}
if (trCollide) {
int k = 0;
}
if (blCollide) {
int k = 0;
}
if (brCollide) {
int k = 0;
}
// Get the vels
double totalXVel = Math.abs(ridgidBody.xVel) + Math.abs(otherRidgidBody.xVel);
double totalYVel = Math.abs(ridgidBody.yVel) + Math.abs(otherRidgidBody.yVel);
double totalMass = ridgidBody.mass + otherRidgidBody.mass;
int xMult = 0;
int yMult = 0;
boolean fullEdge = false;
// left-hand collision
if (tlCollide && blCollide) {
fullEdge = true;
xMult = 2;
}
// Bottom collision
if (blCollide && brCollide) {
fullEdge = true;
yMult = -2;
}
// right-hand collision
if (brCollide && trCollide) {
fullEdge = true;
xMult = -2;
}
// top collision
if (trCollide && tlCollide) {
fullEdge = true;
yMult = 2;
}
if (fullEdge) {
// Finally, actuate the calculation
if (ridgidBody.mass < 0) {
// entity 1 is immovable
otherRidgidBody.xVel += totalXVel * (-1 * xMult);
otherRidgidBody.yVel += totalYVel * (-1 * yMult);
Object grav = gameEngine.getComponentData(entity, Gravity.class);
if (grav != null) {
Gravity gravity = (Gravity) grav;
otherRidgidBody.xAcc += gravity.x * -1.;
otherRidgidBody.yAcc += gravity.y * -1.;
}
} else if (otherRidgidBody.mass < 0) {
// entity 2 is immovable; full acceleration applied to the current entity
ridgidBody.xVel += totalXVel * xMult;
ridgidBody.yVel += totalYVel * yMult;
Object grav = gameEngine.getComponentData(entity, Gravity.class);
if (grav != null) {
// negate gravity while collided
Gravity gravity = (Gravity) grav;
ridgidBody.xAcc += gravity.x * -1.;
ridgidBody.yAcc += gravity.y * -1.;
}
} else {
// collision is elastic; add the forces x & y and the masses, and share between
// objects
/*ridgidBody.xVel += (ridgidBody.mass / totalMass) * totalXVel * xMult;
otherRidgidBody.xVel += (otherRidgidBody.mass / totalMass) * totalXVel
* (-1 * xMult);
ridgidBody.yVel += (ridgidBody.mass / totalMass) * totalYVel * yMult;
otherRidgidBody.yVel += (otherRidgidBody.mass / totalMass) * totalYVel
* (-1 * yMult);*/
}
}
else{
}
}
}
}
}
}
}
/**
* Zero degrees is straight up,
* 90 east
* -90 (270) = west
* 180 south
* @param xVel
* @param yVel
* @return
*/
public static double calcAngle(double xVel, double yVel){
if (xVel == 0){ // Div zero; = +- 90
return yVel < 0 ? 270 : 90;
}
else if (xVel < 0 && yVel < 0){ // bottom-left quad (180 - 270)
return 180. + Math.tanh(yVel/xVel);
}
else if (xVel < 0){ // Top left quad (270 - 360)
return 360 - Math.tanh(yVel/xVel);
}
else if (yVel < 0){
return 180 - Math.tanh(yVel/xVel);
}
else{
return Math.tanh(yVel/xVel);
}
}
}
@@ -0,0 +1,34 @@
package nz.ac.massey.javaecs.examples.Systems;
import java.util.BitSet;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.LogUpdate;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
public class LogUpdateSystem extends ECSSystem{
Engine gameEngine;
public LogUpdateSystem(Engine gameEngine){
this.gameEngine = gameEngine;
// Set registrations
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(LogUpdate.class));
}
@Override
public void init() {
// TODO Auto-generated method stub
}
@Override
public void update() {
for (Entity entity : entities) {
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
System.out.println(String.format("X: %.6g, Y: %.6g", pos.x, pos.y));
}
}
}
@@ -0,0 +1,68 @@
package nz.ac.massey.javaecs.examples.Systems;
import java.util.BitSet;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
import nz.ac.massey.javaecs.examples.Components.RidgidBody;
import nz.ac.massey.javaecs.examples.Components.Collider;
import nz.ac.massey.javaecs.examples.Components.Gravity;
public class PhysicsSystem extends ECSSystem {
Engine gameEngine;
public PhysicsSystem(Engine gameEngine){
this.gameEngine = gameEngine;
// Set registrations
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Vec2D.class));
registrationSet.set(gameEngine.getComponentIndex(RidgidBody.class));
registrationSet.set(gameEngine.getComponentIndex(Gravity.class));
registrationSet.set(gameEngine.getComponentIndex(Collider.class));
}
@Override
public void init() {
// TODO Auto-generated method stub
}
@Override
public void update() {
// TODO Auto-generated method stub
}
public void update(double dt){
for (Entity entity : entities) {
Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
RidgidBody ridgidBody = (RidgidBody)gameEngine.getComponentData(entity, RidgidBody.class);
Gravity gravity = (Gravity)gameEngine.getComponentData(entity, Gravity.class);
Collider collider = (Collider)gameEngine.getComponentData(entity, Collider.class);
// Firstly, add the result of the accelerative forces to the ridgidbody
ridgidBody.xVel += ridgidBody.xAcc * dt;
ridgidBody.yVel += ridgidBody.yAcc * dt;
// Set acceleration to zero (it has been processed/integrated and is now applied to the velocity);
// use a constant-force component to apply acceleration each frame
ridgidBody.xAcc = 0.;
ridgidBody.yAcc = 0.;
if (!collider.collided){
// Special case of gravity
if (gravity.terminalX < 0 || Math.abs(ridgidBody.xVel) < gravity.terminalX){
ridgidBody.xAcc += gravity.x;
}
if (gravity.terminalY < 0 || Math.abs(ridgidBody.yVel) < gravity.terminalY){
ridgidBody.yAcc += gravity.y;
}
}
// Finally, move the vec2d by the new velocity
pos.x += ridgidBody.xVel * dt;
pos.y += ridgidBody.yVel * dt;
}
}
}
@@ -0,0 +1,107 @@
package nz.ac.massey.javaecs.examples.Systems;
import java.util.BitSet;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.paint.Paint;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
import nz.ac.massey.javaecs.ECSSystem;
import nz.ac.massey.javaecs.Engine;
import nz.ac.massey.javaecs.Entity;
import nz.ac.massey.javaecs.examples.Components.BoxRender;
import nz.ac.massey.javaecs.examples.Components.Render;
import nz.ac.massey.javaecs.examples.Components.Vec2D;
public class RenderSystem extends ECSSystem{
JFXView jFXView;
Engine gameEngine;
Thread renderThread;
double renderScale;
int screenX = 1024;
int screenY = 1024;
public RenderSystem(Engine gameEngine, double renderScale){
this.gameEngine = gameEngine;
// Set registrations
registrationSet = new BitSet();
registrationSet.set(gameEngine.getComponentIndex(Render.class));
this.renderScale = renderScale;
}
@Override
public void init() {
// Spawn a new thread
renderThread = new Thread(new RenderStarter());
renderThread.start(); // this thread is asynchronous, it isn't expected to rejoin
}
@Override
public void update() {
/* while (JFXView.root == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} */
Group renderScene = new Group();
for (Entity entity : entities) {
//Vec2D pos = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
if (((Render)gameEngine.getComponentData(entity, Render.class)).renderType == BoxRender.class){
BoxRender box = (BoxRender)gameEngine.getComponentData(entity, BoxRender.class);
Vec2D vec2d = (Vec2D)gameEngine.getComponentData(entity, Vec2D.class);
Rectangle rect = new Rectangle(box.xSize* renderScale, box.ySize * renderScale);
rect.setFill(new Color(box.r, box.g, box.b, 0.9));
rect.setX(12 + (vec2d.x * renderScale));
rect.setY(1012 - (vec2d.y * renderScale) - rect.getHeight());
renderScene.getChildren().add(rect);
}
}
Platform.runLater(new Runnable(){
@Override
public void run() {
JFXView.root.getChildren().clear();
JFXView.root.getChildren().add(renderScene);
}
});
}
// Add a renderer
public class RenderStarter implements Runnable{
@Override
public void run() {
JFXView.run();
}
void updateScene(Group newGroup){
JFXView.root.getChildren().clear();
JFXView.root.getChildren().add(newGroup);
}
}
public static class JFXView extends Application {
private static Group root;
@Override
public void start(Stage primaryStage) throws Exception {
// based on the colorful circles sample at https://docs.oracle.com/javase/8/javafx/get-started-tutorial/animation.htm
root = new Group();
Scene scene = new Scene(root, 1024, 1024, Color.BLACK);
primaryStage.setScene(scene);
primaryStage.show();
}
public static void run(){
launch(new String[0]);
}
}
}
@@ -0,0 +1,18 @@
package nz.ac.massey.javaecs.examples;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Unit test for simple App.
*/
class AppTest {
/**
* Rigorous Test.
*/
@Test
void testApp() {
assertEquals(1, 1);
}
}