commit f3d2740a25d62249b9ab087e7759e4091eccd549 Author: Brychan Dempsey Date: Wed Jun 9 22:36:28 2021 +1200 Added example project doc diff --git a/demo1/.editorconfig b/demo1/.editorconfig new file mode 100644 index 0000000..1f494b0 --- /dev/null +++ b/demo1/.editorconfig @@ -0,0 +1,17 @@ +# Editor configuration, see http://editorconfig.org +root = true + +[*] +charset = utf-8 +indent_style = space +indent_size = 2 +insert_final_newline = true +trim_trailing_whitespace = true +max_line_length = 80 + +[*.sh] +end_of_line = lf + +[*.java] +indent_size = 4 +max_line_length = 120 diff --git a/demo1/.gitattributes b/demo1/.gitattributes new file mode 100644 index 0000000..8dfa1eb --- /dev/null +++ b/demo1/.gitattributes @@ -0,0 +1,2 @@ +# When shell scripts end in CRLF, bash gives a cryptic error message +*.sh text eol=lf diff --git a/demo1/.gitignore b/demo1/.gitignore new file mode 100644 index 0000000..7194620 --- /dev/null +++ b/demo1/.gitignore @@ -0,0 +1,28 @@ +# +# Standard Maven .gitignore +# +target/ +pom.xml.tag +pom.xml.releaseBackup +pom.xml.versionsBackup +pom.xml.next +release.properties +dependency-reduced-pom.xml +buildNumber.properties +.mvn/timing.properties + +# +# IntelliJ +# +*.iml +.idea/* +!.idea/runConfigurations/ + +# +# Visual Studio Code +# +.settings/ +.classpath +.factorypath +.project +.vscode/ diff --git a/demo1/.travis.yml b/demo1/.travis.yml new file mode 100644 index 0000000..8bdbb2f --- /dev/null +++ b/demo1/.travis.yml @@ -0,0 +1,4 @@ +language: java +jdk: openjdk8 +after_success: + - mvn coveralls:report diff --git a/demo1/pom.xml b/demo1/pom.xml new file mode 100644 index 0000000..a460062 --- /dev/null +++ b/demo1/pom.xml @@ -0,0 +1,200 @@ + + 4.0.0 + nz.ac.massey.javaecs.examples + demo1 + 1.0-SNAPSHOT + + 1.8 + 1.8 + UTF-8 + 5.6.0 + 3.0.0-M3 + 3.1.0 + 8.29 + 4.0.1 + 3.0.0-M4 + 0.8.4 + 3.0.0 + 4.3.0 + + 0% + 0% + 20 + 5 + + + + org.junit.jupiter + junit-jupiter-api + ${junit.version} + test + + + org.junit.jupiter + junit-jupiter-engine + ${junit.version} + test + + + nz.ac.massey.javaecs + javaecs + 0.9.2-PRERELEASE + + + + + + org.apache.maven.plugins + maven-enforcer-plugin + ${maven-enforcer-plugin.version} + + + + enforce + + + + + 3.6.3 + + + true + + + + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${maven-checkstyle-plugin.version} + + + com.puppycrawl.tools + checkstyle + ${checkstyle.version} + + + com.github.ngeor + checkstyle-rules + ${checkstyle-rules.version} + + + + com/github/ngeor/checkstyle.xml + true + ${skipTests} + + + + checkstyle + validate + + check + + + + + + org.apache.maven.plugins + maven-surefire-plugin + ${maven-surefire-plugin.version} + + + org.jacoco + jacoco-maven-plugin + ${jacoco-maven-plugin.version} + + + pre-unit-test + + prepare-agent + + + + post-unit-test + test + + report + + + + check-unit-test + test + + check + + + ${project.build.directory}/jacoco.exec + + + BUNDLE + + + INSTRUCTION + COVEREDRATIO + ${jacoco.unit-tests.limit.instruction-ratio} + + + BRANCH + COVEREDRATIO + ${jacoco.unit-tests.limit.branch-ratio} + + + + + CLASS + + + COMPLEXITY + TOTALCOUNT + ${jacoco.unit-tests.limit.class-complexity} + + + + + METHOD + + + COMPLEXITY + TOTALCOUNT + ${jacoco.unit-tests.limit.method-complexity} + + + + + + + + + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + ${maven-javadoc-plugin.version} + + + + + + + travis + + + env.TRAVIS + + + + + + org.eluder.coveralls + coveralls-maven-plugin + ${coveralls-maven-plugin.version} + + + + + + diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/App.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/App.java new file mode 100644 index 0000000..dbf57a9 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/App.java @@ -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(); + } + } + } +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/BoxRender.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/BoxRender.java new file mode 100644 index 0000000..a68e999 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/BoxRender.java @@ -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.; + } +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Collider.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Collider.java new file mode 100644 index 0000000..c4adca0 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Collider.java @@ -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; +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Gravity.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Gravity.java new file mode 100644 index 0000000..2e7c641 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Gravity.java @@ -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; +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/LogUpdate.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/LogUpdate.java new file mode 100644 index 0000000..de03d40 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/LogUpdate.java @@ -0,0 +1,5 @@ +package nz.ac.massey.javaecs.examples.Components; + +public class LogUpdate { + +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Render.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Render.java new file mode 100644 index 0000000..27e2f02 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Render.java @@ -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; + } +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/RidgidBody.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/RidgidBody.java new file mode 100644 index 0000000..4781db0 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/RidgidBody.java @@ -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; +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Vec2D.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Vec2D.java new file mode 100644 index 0000000..f4c3d21 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Components/Vec2D.java @@ -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; +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/CollisionSystem.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/CollisionSystem.java new file mode 100644 index 0000000..6ab9615 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/CollisionSystem.java @@ -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 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 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); + } + + } + +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/LogUpdateSystem.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/LogUpdateSystem.java new file mode 100644 index 0000000..4329609 --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/LogUpdateSystem.java @@ -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)); + } + + } +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/PhysicsSystem.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/PhysicsSystem.java new file mode 100644 index 0000000..20be95b --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/PhysicsSystem.java @@ -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; + } + } +} diff --git a/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java new file mode 100644 index 0000000..33edacb --- /dev/null +++ b/demo1/src/main/java/nz/ac/massey/javaecs/examples/Systems/RenderSystem.java @@ -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]); + } + } +} diff --git a/demo1/src/test/java/nz/ac/massey/javaecs/examples/AppTest.java b/demo1/src/test/java/nz/ac/massey/javaecs/examples/AppTest.java new file mode 100644 index 0000000..1978087 --- /dev/null +++ b/demo1/src/test/java/nz/ac/massey/javaecs/examples/AppTest.java @@ -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); + } +}