diff --git a/.gitignore b/.gitignore index f32e792..dd7fd31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,248 @@ -.idea/ -out/ -build/ +.project +.classpath + +# Eclipse +.metadata +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath +.recommenders + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# PyDev specific (Python IDE for Eclipse) +*.pydevproject + +# CDT-specific (C/C++ Development Tooling) +.cproject + +# CDT- autotools +.autotools + +# Java annotation processor (APT) +.factorypath + +# PDT-specific (PHP Development Tools) +.buildpath + +# sbteclipse plugin +.target + +# Tern plugin +.tern-project + +# TeXlipse plugin +.texlipse + +# STS (Spring Tool Suite) +.springBeans + +# Code Recommenders +.recommenders/ + +# Annotation Processing +.apt_generated/ +.apt_generated_test/ + +# Scala IDE specific (Scala & Java development for Eclipse) +.cache-main +.scala_dependencies +.worksheet + +# Uncomment this line if you wish to ignore the project description file. +# Typically, this file would be tracked if it contains build/dependency configurations: +#.project + +# Gradle .gradle -__pycache__ -*.iml \ No newline at end of file +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# static files generated from Django application using `collectstatic` +media +static diff --git a/src/main/java/rlbotexample/SampleBot.java b/src/main/java/rlbotexample/SampleBot.java index 0a82f17..d74cc35 100644 --- a/src/main/java/rlbotexample/SampleBot.java +++ b/src/main/java/rlbotexample/SampleBot.java @@ -1,10 +1,11 @@ package rlbotexample; +import java.awt.Color; +import java.awt.Point; + import rlbot.Bot; import rlbot.ControllerState; import rlbot.cppinterop.RLBotDll; -import rlbot.cppinterop.RLBotInterfaceException; -import rlbot.flat.BallPrediction; import rlbot.flat.GameTickPacket; import rlbot.flat.QuickChatSelection; import rlbot.manager.BotLoopRenderer; @@ -12,117 +13,143 @@ import rlbotexample.boost.BoostManager; import rlbotexample.input.DataPacket; import rlbotexample.input.car.CarData; -import rlbotexample.output.ControlsOutput; +import rlbotexample.manoeuvre.Dodge; +import rlbotexample.manoeuvre.Manoeuvre; +import rlbotexample.manoeuvre.Recovery; +import rlbotexample.manoeuvre.sequence.ControlStep; +import rlbotexample.manoeuvre.sequence.Sequence; +import rlbotexample.output.Controls; +import rlbotexample.prediction.BallPrediction; import rlbotexample.prediction.BallPredictionHelper; +import rlbotexample.prediction.slice.PositionSlice; +import rlbotexample.util.MathUtils; import rlbotexample.vector.Vector2; - -import java.awt.*; +import rlbotexample.vector.Vector3; public class SampleBot implements Bot { - private final int playerIndex; - - public SampleBot(int playerIndex) { - this.playerIndex = playerIndex; - } - - /** - * This is where we keep the actual bot logic. This function shows how to chase the ball. - * Modify it to make your bot smarter! - */ - private ControlsOutput processInput(DataPacket input) { - - Vector2 ballPosition = input.ball.position.flatten(); - CarData myCar = input.car; - Vector2 carPosition = myCar.position.flatten(); - Vector2 carDirection = myCar.orientation.noseVector.flatten(); - - // Subtract the two positions to get a vector pointing from the car to the ball. - Vector2 carToBall = ballPosition.minus(carPosition); - - // How far does the car need to rotate before it's pointing exactly at the ball? - double steerCorrectionRadians = carDirection.correctionAngle(carToBall); - - boolean goLeft = steerCorrectionRadians > 0; - - // This is optional! - drawDebugLines(input, myCar, goLeft); - - // This is also optional! - if (input.ball.position.z > 300) { - RLBotDll.sendQuickChat(playerIndex, false, QuickChatSelection.Compliments_NiceOne); - } - - return new ControlsOutput() - .withSteer(goLeft ? -1 : 1) - .withThrottle(1); - } - - /** - * This is a nice example of using the rendering feature. - */ - private void drawDebugLines(DataPacket input, CarData myCar, boolean goLeft) { - // Here's an example of rendering debug data on the screen. - Renderer renderer = BotLoopRenderer.forBotLoop(this); - - // Draw a line from the car to the ball - renderer.drawLine3d(Color.LIGHT_GRAY, myCar.position, input.ball.position); - - // Draw a line that points out from the nose of the car. - renderer.drawLine3d(goLeft ? Color.BLUE : Color.RED, - myCar.position.plus(myCar.orientation.noseVector.scaled(150)), - myCar.position.plus(myCar.orientation.noseVector.scaled(300))); - - renderer.drawString3d(goLeft ? "left" : "right", Color.WHITE, myCar.position, 2, 2); - - if(input.ball.hasBeenTouched) { - float lastTouchTime = myCar.elapsedSeconds - input.ball.latestTouch.gameSeconds; - Color touchColor = input.ball.latestTouch.team == 0 ? Color.BLUE : Color.ORANGE; - renderer.drawString3d((int)lastTouchTime + "s", touchColor, input.ball.position, 2, 2); - } - - try { - // Draw 3 seconds of ball prediction - BallPrediction ballPrediction = RLBotDll.getBallPrediction(); - BallPredictionHelper.drawTillMoment(ballPrediction, myCar.elapsedSeconds + 3, Color.CYAN, renderer); - } catch (RLBotInterfaceException e) { - e.printStackTrace(); - } - } - - - @Override - public int getIndex() { - return this.playerIndex; - } - - /** - * This is the most important function. It will automatically get called by the framework with fresh data - * every frame. Respond with appropriate controls! - */ - @Override - public ControllerState processInput(GameTickPacket packet) { - - if (packet.playersLength() <= playerIndex || packet.ball() == null || !packet.gameInfo().isRoundActive()) { - // Just return immediately if something looks wrong with the data. This helps us avoid stack traces. - return new ControlsOutput(); - } - - // Update the boost manager and tile manager with the latest data - BoostManager.loadGameTickPacket(packet); - - // Translate the raw packet data (which is in an unpleasant format) into our custom DataPacket class. - // The DataPacket might not include everything from GameTickPacket, so improve it if you need to! - DataPacket dataPacket = new DataPacket(packet, playerIndex); - - // Do the actual logic using our dataPacket. - ControlsOutput controlsOutput = processInput(dataPacket); - - return controlsOutput; - } - - @Override - public void retire() { - System.out.println("Retiring sample bot " + playerIndex); - } + private final int playerIndex; + + private Manoeuvre manoeuvre; + + public SampleBot(int playerIndex) { + this.playerIndex = playerIndex; + } + + /** + * This is where we keep the actual bot logic. This function shows how to chase + * the ball. Modify it to make your bot smarter! + */ + private Controls processInput(DataPacket packet) { + if (this.manoeuvre != null) { + if (this.manoeuvre.isFinished()) { + this.manoeuvre = null; + } else { + return this.manoeuvre.tick(packet); + } + } + + CarData car = packet.car; + Vector3 ballPosition = packet.ball.position; + Vector3 localBall = MathUtils.toLocal(car, ballPosition); + + // How far does the car need to rotate before it's pointing exactly at the ball? + float angle = (float) Vector2.X.correctionAngle(localBall.flatten()); + + if (car.hasWheelContact) { + double carSpeed = car.velocity.magnitude(); + if (!car.isSupersonic && carSpeed > 1200 && car.boost < 10) { + this.manoeuvre = new Dodge(angle * 2); + return this.manoeuvre.tick(packet); + } + } else { + this.manoeuvre = new Recovery(packet.car); + return this.manoeuvre.tick(packet); + } + + // This is optional! + drawDebugLines(packet, car); + + // This is also optional! + if (packet.ball.position.z > 300) { + RLBotDll.sendQuickChat(playerIndex, false, QuickChatSelection.Compliments_NiceOne); + } + + return new Controls().withSteer(MathUtils.clamp11(angle * 5)).withThrottle(1) + .withBoost(!car.isSupersonic && Math.abs(angle) < Math.toRadians(25)); + } + + /** + * This is a nice example of using the rendering feature. + */ + private void drawDebugLines(DataPacket packet, CarData car) { + // Here's an example of rendering debug data on the screen. + Renderer renderer = BotLoopRenderer.forBotLoop(this); + + // Draw a line from the car to the ball + renderer.drawLine3d(Color.LIGHT_GRAY, car.position, packet.ball.position); + + // Draw a line that points out from the nose of the car. + renderer.drawLine3d(car.team == 0 ? Color.BLUE : Color.ORANGE, + car.position.plus(car.orientation.forward.scaled(150)), + car.position.plus(car.orientation.forward.scaled(300))); + + if (packet.ball.hasBeenTouched) { + float lastTouchTime = car.elapsedSeconds - packet.ball.latestTouch.gameSeconds; + Color touchColor = packet.ball.latestTouch.team == 0 ? Color.BLUE : Color.ORANGE; + renderer.drawString3d((int) lastTouchTime + "s", touchColor, packet.ball.position, 2, 2); + } + + // Draw 3 seconds of ball prediction + BallPredictionHelper.drawTillMoment(car.elapsedSeconds + 3, Color.CYAN, renderer); + + PositionSlice futureGoal = BallPredictionHelper.findFutureGoal(); + if (futureGoal == null) { + renderer.drawString2d("No future goal", Color.WHITE, new Point(20, 20), 2, 2); + } else { + renderer.drawString2d("Goal in " + + MathUtils.round(futureGoal.getElapsedSeconds() - packet.elapsedSeconds, 2) + " seconds!", + Color.RED, new Point(20, 20), 2, 2); + } + } + + @Override + public int getIndex() { + return this.playerIndex; + } + + /** + * This is the most important function. It will automatically get called by the + * framework with fresh data every frame. Respond with appropriate controls! + */ + @Override + public ControllerState processInput(GameTickPacket gameTickPacket) { + + if (gameTickPacket.playersLength() <= playerIndex || gameTickPacket.ball() == null) { + // Just return immediately if something looks wrong with the data. This helps us + // avoid stack traces. + return new Controls(); + } + + // Update the boost manager and tile manager with the latest data + BoostManager.loadGameTickPacket(gameTickPacket); + + BallPrediction.update(); + + // Translate the raw packet data (which is in an unpleasant format) into our + // custom DataPacket class. + // The DataPacket might not include everything from GameTickPacket, so improve + // it if you need to! + DataPacket packet = new DataPacket(gameTickPacket, playerIndex); + + // Do the actual logic using our dataPacket. + Controls controlsOutput = processInput(packet); + + return controlsOutput; + } + + @Override + public void retire() { + System.out.println("Retiring sample bot " + playerIndex); + } } diff --git a/src/main/java/rlbotexample/input/DataPacket.java b/src/main/java/rlbotexample/input/DataPacket.java index a61b69c..ca18193 100644 --- a/src/main/java/rlbotexample/input/DataPacket.java +++ b/src/main/java/rlbotexample/input/DataPacket.java @@ -26,15 +26,18 @@ public class DataPacket { /** The index of your player */ public final int playerIndex; + + public final float elapsedSeconds; public DataPacket(GameTickPacket request, int playerIndex) { + this.elapsedSeconds = request.gameInfo().secondsElapsed(); this.playerIndex = playerIndex; this.ball = new BallData(request.ball()); allCars = new ArrayList<>(); for (int i = 0; i < request.playersLength(); i++) { - allCars.add(new CarData(request.players(i), request.gameInfo().secondsElapsed())); + allCars.add(new CarData(request.players(i), this.elapsedSeconds)); } this.car = allCars.get(playerIndex); diff --git a/src/main/java/rlbotexample/input/ModernBallPrediction.java b/src/main/java/rlbotexample/input/ModernBallPrediction.java new file mode 100644 index 0000000..460de45 --- /dev/null +++ b/src/main/java/rlbotexample/input/ModernBallPrediction.java @@ -0,0 +1,218 @@ +package rlbotexample.input; + +import rlbot.cppinterop.RLBotDll; +import rlbot.cppinterop.RLBotInterfaceException; +import rlbot.flat.BallPrediction; +import rlbot.flat.Physics; +import rlbot.flat.PredictionSlice; +import rlbotexample.input.ball.BallData; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +public class ModernBallPrediction { + + public final int tickRate; // close to 60 or 120 + public final float tickFrequency; // close to 1/60 or 1/120 + public final List frames; + + private ModernBallPrediction(List frames, float tickFrequency) { + if (frames == null) + frames = new ArrayList<>(); + float latencyCompensation = RLConstants.gameLatencyCompensation; + if (latencyCompensation > 0) { + List newFrames = new ArrayList<>(frames.size()); + for (ModernPredictionFrame frame : frames) { + if (frame.relativeTime < latencyCompensation) + continue; + + frame.adjustForLatencyCompensation(latencyCompensation); + newFrames.add(frame); + } + frames = newFrames; + } + frames = Collections.unmodifiableList(frames); + + this.frames = frames; + this.tickFrequency = tickFrequency; + this.tickRate = Math.round(1 / tickFrequency); + } + + public static ModernBallPrediction from(List frames, float tickFrequency) { + return new ModernBallPrediction(frames, tickFrequency); + } + + public static ModernBallPrediction empty() { + return new ModernBallPrediction(null, RLConstants.tickFrequency); + } + + private static ModernBallPrediction from(BallPrediction ballPrediction) { + assert ballPrediction.slicesLength() > 0 : "RLBot Ball Prediction has no frames"; + + List frames = new ArrayList<>(ballPrediction.slicesLength()); + + float startTime = ballPrediction.slices(0).gameSeconds(); + float lastTime = startTime; + float averageDt = 0; + + for (int i = 0; i < ballPrediction.slicesLength(); i++) { + PredictionSlice slice = ballPrediction.slices(i); + frames.add(new ModernPredictionFrame(slice.gameSeconds() - startTime, slice)); + + averageDt += slice.gameSeconds() - lastTime; + lastTime = slice.gameSeconds(); + } + + averageDt /= ballPrediction.slicesLength(); + + return new ModernBallPrediction(frames, averageDt); + } + + public static ModernBallPrediction get() { + return get(BallPredictionType.RLBOT); + } + + public static ModernBallPrediction get(BallPredictionType ballPredictionType) { + switch (ballPredictionType) { + case RLBOT: + try { + return ModernBallPrediction.from(RLBotDll.getBallPrediction()); + } catch (RLBotInterfaceException e) { + System.err.println("Could not get RLBot ball Prediction!"); + throw new RuntimeException(e); + } + } + + throw new IllegalStateException("Ball Prediction Type '" + ballPredictionType.name() + "' not recognized"); + } + + /*public void draw(AdvancedRenderer renderer, Color color, float length) { + if (this.frames.size() == 0) + return; + if (length <= 0) + length = this.relativeTimeOfLastFrame(); + + float time = 0; + float lastAbsTime = this.frames.get(0).absoluteTime; + Vector3 lastPos = this.frames.get(0).ballData.position; + while (time < length) { + Optional frame = this.getFrameAfterRelativeTime(time); + if (!frame.isPresent()) + break; + + if (Math.floor(lastAbsTime) < Math.floor(frame.get().absoluteTime)) { + renderer.drawLine3d(color.brighter(), frame.get().ballData.position, frame.get().ballData.position.add(0, 0, 50)); + } + lastAbsTime = frame.get().absoluteTime; + time = frame.get().relativeTime; + ImmutableBallData ball = frame.get().ballData; + + if (ball.makeMutable().isInAnyGoal()) { + renderer.drawCentered3dCube(color.brighter().brighter(), ball.position, 50); + renderer.drawString3d(String.format("Goal! (%.1f)", time), Color.WHITE, ball.position.add(0, 0, 150), 1, 1); + renderer.drawLine3d(color, lastPos, ball.position); + break; + } + + if (lastPos.distance(ball.position) < 50) + continue; + renderer.drawLine3d(color, lastPos, ball.position); + lastPos = ball.position; + } + }*/ + + public float relativeTimeOfLastFrame() { + if (this.frames.size() == 0) + return -1; + return this.frames.get(this.frames.size() - 1).relativeTime; + } + + public ModernBallPrediction trim(float relativeStartTime, float relativeEndTime) { + if (relativeEndTime < relativeStartTime) + throw new IllegalArgumentException("Relative end time smaller than relative start time"); + if (relativeEndTime - relativeStartTime == 0) + return ModernBallPrediction.empty(); + + return new ModernBallPrediction(this.getFramesBetweenRelative(relativeStartTime, relativeEndTime), this.tickFrequency); + } + + public Optional getFrameAtRelativeTime(float relativeTime) { + return this.frames + .stream() + .filter((f) -> f.relativeTime >= relativeTime) + .findFirst(); + } + + public Optional getFrameAfterRelativeTime(float relativeTime) { + return this.frames + .stream() + .filter((f) -> f.relativeTime > relativeTime) + .findFirst(); + } + + public Optional getFrameAtAbsoluteTime(float absolute) { + return this.frames + .stream() + .filter((f) -> f.absoluteTime >= absolute) + .findFirst(); + } + + public List getFramesBeforeRelative(float relativeTime) { + return this.frames + .stream() + .filter((f) -> f.relativeTime < relativeTime) + .collect(Collectors.toList()); + } + + public ModernBallPrediction getBeforeRelative(float relativeTime) { + return new ModernBallPrediction(getFramesBeforeRelative(relativeTime), this.tickFrequency); + } + + public List getFramesAfterRelative(float relativeTime) { + return this.frames + .stream() + .filter((f) -> f.relativeTime > relativeTime) + .collect(Collectors.toList()); + } + + public List getFramesBetweenRelative(float start, float end) { + return this.frames + .stream() + .filter((f) -> f.relativeTime > start && f.relativeTime < end) + .collect(Collectors.toList()); + } + + public boolean hasFrames() { + return this.frames.size() > 0; + } + + enum BallPredictionType { + RLBOT + } + + public static class ModernPredictionFrame { + public final float absoluteTime; + public final BallData ballData; + public float relativeTime; + + public ModernPredictionFrame(float absoluteTime, float relativeTime, BallData ballData) { + this.absoluteTime = absoluteTime; + this.relativeTime = relativeTime; + this.ballData = ballData; + } + + public ModernPredictionFrame(float relativeTime, PredictionSlice predictionSlice) { + this.relativeTime = relativeTime; + this.absoluteTime = predictionSlice.gameSeconds(); + Physics physics = predictionSlice.physics(); + this.ballData = new BallData(physics); + } + + public void adjustForLatencyCompensation(float offset) { + relativeTime -= offset; + } + } +} diff --git a/src/main/java/rlbotexample/input/RLConstants.java b/src/main/java/rlbotexample/input/RLConstants.java new file mode 100644 index 0000000..7a5a162 --- /dev/null +++ b/src/main/java/rlbotexample/input/RLConstants.java @@ -0,0 +1,43 @@ +package rlbotexample.input; + +import rlbotexample.vector.Vector2; +import rlbotexample.vector.Vector3; + +public class RLConstants { + + public static final float carElevation = 17.01f; + + public static final float goalDistance = 5120f; // Distance from center to goal + public static final float goalCenterToPost = 892.755f; + public static final float goalHeight = 642.775f; + + public static final float arenaHeight = 2044; + public static final float arenaWidth = 8192; + public static final float arenaHalfWidth = arenaWidth / 2; + public static final float arenaLength = 10240; + public static final float arenaHalfLength = arenaLength / 2; + + public static Vector3 gravity = new Vector3(0, 0, -650); + + public static float gameLatencyCompensation = 0.f; + + public static int tickRate = 120; + public static float tickFrequency = 1f / tickRate; + + public static int simulationTickRate = 60; + public static float simulationTickFrequency = 1f / simulationTickRate; + + public static boolean isPosNearWall(Vector2 pos, float tolerance) { + float x = (float) Math.abs(pos.x) + tolerance; + float y = (float) Math.abs(pos.y); + + if (x >= 2700 - tolerance && x <= 3850 + tolerance) { + // https://www.wolframalpha.com/input/?i=Line+between+%283850%2C+3850%29+%282700%2C+4950%29 + float val = 173250f / 23f - (22f * x) / 23f; + if (val <= y + tolerance) + return true; + } + + return x >= 3820 || y + tolerance >= 4920; + } +} diff --git a/src/main/java/rlbotexample/input/Rotator.java b/src/main/java/rlbotexample/input/Rotator.java new file mode 100644 index 0000000..e1b5520 --- /dev/null +++ b/src/main/java/rlbotexample/input/Rotator.java @@ -0,0 +1,23 @@ +package rlbotexample.input; + +import rlbotexample.input.car.CarOrientation; +import rlbotexample.vector.Vector3; + +public class Rotator { + + public final float pitch, roll, yaw; + + public Rotator(float pitch, float roll, float yaw) { + super(); + this.pitch = pitch; + this.roll = roll; + this.yaw = yaw; + } + + public Rotator(CarOrientation orientation, Vector3 vector) { + this.pitch = (float) vector.dot(orientation.right); + this.roll = (float) vector.dot(orientation.forward); + this.yaw = (float) vector.dot(orientation.up); + } + +} diff --git a/src/main/java/rlbotexample/input/ball/BallData.java b/src/main/java/rlbotexample/input/ball/BallData.java index 8dffab4..26a58b0 100644 --- a/src/main/java/rlbotexample/input/ball/BallData.java +++ b/src/main/java/rlbotexample/input/ball/BallData.java @@ -2,6 +2,7 @@ import rlbot.flat.BallInfo; +import rlbot.flat.Physics; import rlbotexample.vector.Vector3; /** @@ -24,4 +25,12 @@ public BallData(final BallInfo ball) { this.hasBeenTouched = ball.latestTouch() != null; this.latestTouch = this.hasBeenTouched ? new BallTouch(ball.latestTouch()) : null; } + + public BallData(final Physics ballPhysics) { + this.position = new Vector3(ballPhysics.location()); + this.velocity = new Vector3(ballPhysics.velocity()); + this.spin = new Vector3(ballPhysics.angularVelocity()); + this.hasBeenTouched = false; + this.latestTouch = null; + } } diff --git a/src/main/java/rlbotexample/input/car/CarData.java b/src/main/java/rlbotexample/input/car/CarData.java index 1023e73..cead117 100644 --- a/src/main/java/rlbotexample/input/car/CarData.java +++ b/src/main/java/rlbotexample/input/car/CarData.java @@ -1,56 +1,70 @@ package rlbotexample.input.car; +import rlbotexample.input.Rotator; + +import rlbotexample.vector.Matrix3x3; import rlbotexample.vector.Vector3; /** * Basic information about the car. * - * This class is here for your convenience, it is NOT part of the framework. You can change it as much - * as you want, or delete it. + * This class is here for your convenience, it is NOT part of the framework. You + * can change it as much as you want, or delete it. */ public class CarData { - /** The location of the car on the field. (0, 0, 0) is center field. */ - public final Vector3 position; + /** The location of the car on the field. (0, 0, 0) is center field. */ + public final Vector3 position; + + /** The velocity of the car. */ + public final Vector3 velocity; - /** The velocity of the car. */ - public final Vector3 velocity; + /** The orientation of the car */ + public final CarOrientation orientation; - /** The orientation of the car */ - public final CarOrientation orientation; + public final Matrix3x3 matOrientation; /** Boost ranges from 0 to 100 */ public final double boost; - /** True if the car is driving on the ground, the wall, etc. In other words, true if you can steer. */ - public final boolean hasWheelContact; - - /** - * True if the car is showing the supersonic and can demolish enemies on contact. - * This is a close approximation for whether the car is at max speed. - */ - public final boolean isSupersonic; - - /** - * 0 for blue team, 1 for orange team. - */ - public final int team; - - /** - * This is not really a car-specific attribute, but it's often very useful to know. It's included here - * so you don't need to pass around DataPacket everywhere. - */ - public final float elapsedSeconds; - - public CarData(rlbot.flat.PlayerInfo playerInfo, float elapsedSeconds) { - this.position = new Vector3(playerInfo.physics().location()); - this.velocity = new Vector3(playerInfo.physics().velocity()); - this.orientation = CarOrientation.fromFlatbuffer(playerInfo); + /** + * True if the car is driving on the ground, the wall, etc. In other words, true + * if you can steer. + */ + public final boolean hasWheelContact; + + /** + * True if the car is showing the supersonic and can demolish enemies on + * contact. This is a close approximation for whether the car is at max speed. + */ + public final boolean isSupersonic; + + /** + * 0 for blue team, 1 for orange team. + */ + public final int team; + + /** + * This is not really a car-specific attribute, but it's often very useful to + * know. It's included here so you don't need to pass around DataPacket + * everywhere. + */ + public final float elapsedSeconds; + + public final Rotator angularVelocity; + + public CarData(rlbot.flat.PlayerInfo playerInfo, float elapsedSeconds) { + this.position = new Vector3(playerInfo.physics().location()); + this.velocity = new Vector3(playerInfo.physics().velocity()); + this.orientation = CarOrientation.fromFlatbuffer(playerInfo); + this.angularVelocity = new Rotator(this.orientation, new Vector3(playerInfo.physics().angularVelocity())); this.boost = playerInfo.boost(); this.isSupersonic = playerInfo.isSupersonic(); this.team = playerInfo.team(); this.hasWheelContact = playerInfo.hasWheelContact(); this.elapsedSeconds = elapsedSeconds; + rlbot.flat.Rotator r = playerInfo.physics().rotation(); + this.matOrientation = Matrix3x3.eulerToRotation(new Vector3(r.pitch(), r.yaw(), r.roll())); } } diff --git a/src/main/java/rlbotexample/input/car/CarOrientation.java b/src/main/java/rlbotexample/input/car/CarOrientation.java index 68370ec..539ed8a 100644 --- a/src/main/java/rlbotexample/input/car/CarOrientation.java +++ b/src/main/java/rlbotexample/input/car/CarOrientation.java @@ -1,53 +1,54 @@ package rlbotexample.input.car; - import rlbot.flat.PlayerInfo; +import rlbotexample.input.Rotator; import rlbotexample.vector.Vector3; /** * The car's orientation in space, a.k.a. what direction it's pointing. * - * This class is here for your convenience, it is NOT part of the framework. You can change it as much - * as you want, or delete it. + * This class is here for your convenience, it is NOT part of the framework. You + * can change it as much as you want, or delete it. */ -public class CarOrientation { - - /** The direction that the front of the car is facing */ - public final Vector3 noseVector; - - /** The direction the roof of the car is facing. (0, 0, 1) means the car is upright. */ - public final Vector3 roofVector; - - /** The direction that the right side of the car is facing. */ - public final Vector3 rightVector; - - public CarOrientation(Vector3 noseVector, Vector3 roofVector) { - - this.noseVector = noseVector; - this.roofVector = roofVector; - this.rightVector = noseVector.crossProduct(roofVector); - } - - public static CarOrientation fromFlatbuffer(PlayerInfo playerInfo) { - return convert( - playerInfo.physics().rotation().pitch(), - playerInfo.physics().rotation().yaw(), - playerInfo.physics().rotation().roll()); - } - - /** - * All params are in radians. - */ - private static CarOrientation convert(double pitch, double yaw, double roll) { - - double noseX = -1 * Math.cos(pitch) * Math.cos(yaw); - double noseY = Math.cos(pitch) * Math.sin(yaw); - double noseZ = Math.sin(pitch); - - double roofX = Math.cos(roll) * Math.sin(pitch) * Math.cos(yaw) + Math.sin(roll) * Math.sin(yaw); - double roofY = Math.cos(yaw) * Math.sin(roll) - Math.cos(roll) * Math.sin(pitch) * Math.sin(yaw); - double roofZ = Math.cos(roll) * Math.cos(pitch); - - return new CarOrientation(new Vector3(noseX, noseY, noseZ), new Vector3(roofX, roofY, roofZ)); - } +public class CarOrientation extends Rotator { + + /** The direction that the front of the car is facing */ + public final Vector3 forward; + + /** + * The direction the roof of the car is facing. (0, 0, 1) means the car is + * upright. + */ + public final Vector3 up; + + /** The direction that the right side of the car is facing. */ + public final Vector3 right; + + public CarOrientation(Vector3 noseVector, Vector3 roofVector, double pitch, double yaw, double roll) { + super((float) pitch, (float) roll, (float) yaw); + this.forward = noseVector; + this.up = roofVector; + this.right = noseVector.cross(roofVector); + } + + public static CarOrientation fromFlatbuffer(PlayerInfo playerInfo) { + return convert(playerInfo.physics().rotation().pitch(), playerInfo.physics().rotation().yaw(), + playerInfo.physics().rotation().roll()); + } + + /** + * All params are in radians. + */ + private static CarOrientation convert(double pitch, double yaw, double roll) { + + double noseX = -1 * Math.cos(pitch) * Math.cos(yaw); + double noseY = Math.cos(pitch) * Math.sin(yaw); + double noseZ = Math.sin(pitch); + + double roofX = Math.cos(roll) * Math.sin(pitch) * Math.cos(yaw) + Math.sin(roll) * Math.sin(yaw); + double roofY = Math.cos(yaw) * Math.sin(roll) - Math.cos(roll) * Math.sin(pitch) * Math.sin(yaw); + double roofZ = Math.cos(roll) * Math.cos(pitch); + + return new CarOrientation(new Vector3(noseX, noseY, noseZ), new Vector3(roofX, roofY, roofZ), pitch, yaw, roll); + } } diff --git a/src/main/java/rlbotexample/manoeuvre/AerialTurn.java b/src/main/java/rlbotexample/manoeuvre/AerialTurn.java new file mode 100644 index 0000000..2f2ef26 --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/AerialTurn.java @@ -0,0 +1,67 @@ +package rlbotexample.manoeuvre; + +import rlbotexample.input.DataPacket; +import rlbotexample.input.car.CarData; +import rlbotexample.output.Controls; +import rlbotexample.vector.Vector3; + +public class AerialTurn extends Manoeuvre { + + private Vector3 targetForward, targetUp; + private Controls controls; + + public AerialTurn(Vector3 targetForward, Vector3 targetUp) { + this.targetForward = targetForward.normalized(); + this.targetUp = targetUp.normalized(); + this.controls = new Controls(); + } + + public AerialTurn(Vector3 targetForward) { + this(targetForward, Vector3.Z); + } + + /** + * https://github.com/DomNomNom/RocketBot/blob/32e69df4f2841501c5f1da97ce34673dccb670af/NomBot_v1.5/NomBot_v1_5.py#L56-L103 + */ + public Controls tick(DataPacket packet) { + CarData car = packet.car; + double pitchVel = car.angularVelocity.pitch; + double yawVel = -car.angularVelocity.yaw; + double rollVel = car.angularVelocity.roll; + + Vector3 desiredFacingAngVel = car.orientation.forward.cross(this.targetForward); + Vector3 desiredUpVel = car.orientation.up.cross(this.targetUp); + + double pitch = desiredFacingAngVel.dot(car.orientation.right); + double yaw = -desiredFacingAngVel.dot(car.orientation.up); + double roll = desiredUpVel.dot(car.orientation.forward); + + // Avoid getting stuck in directly-opposite states + if (car.orientation.up.dot(this.targetUp) < -0.8 && car.orientation.forward.dot(this.targetForward) > 0.8) { + if (roll == 0) + roll = 1; + roll *= 1e10; + } + if (car.orientation.forward.dot(this.targetForward) < -0.8) { + if (pitch == 0) + pitch = 1; + pitch *= 1e10; + } + + // PID control to stop overshooting. + roll = 3 * roll + 0.30 * rollVel; + yaw = 3 * yaw + 0.70 * yawVel; + pitch = 3 * pitch + 0.90 * pitchVel; + + // Only start adjusting roll once we're roughly facing the right way. + if (car.orientation.forward.dot(this.targetForward) < 0) { + roll = 0; + } + + this.setFinished( + car.orientation.forward.dot(this.targetForward) > 0.9 && car.orientation.up.dot(this.targetUp) > 0.7); + + return this.controls.withPitch(pitch).withRoll(roll).withYaw(yaw); + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/Dodge.java b/src/main/java/rlbotexample/manoeuvre/Dodge.java new file mode 100644 index 0000000..35158ff --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/Dodge.java @@ -0,0 +1,16 @@ +package rlbotexample.manoeuvre; + +import rlbotexample.manoeuvre.sequence.ControlStep; +import rlbotexample.manoeuvre.sequence.Sequence; +import rlbotexample.output.Controls; + +public class Dodge extends Sequence { + + public Dodge(float angle) { + super(new ControlStep(0.05, new Controls().withJump()), new ControlStep(0.05, new Controls()), + new ControlStep(0.2, + new Controls().withJump().withPitch((float) -Math.cos(angle)).withYaw((float) Math.sin(angle))), + new ControlStep(0.8, new Controls())); + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/Jump.java b/src/main/java/rlbotexample/manoeuvre/Jump.java new file mode 100644 index 0000000..841d26b --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/Jump.java @@ -0,0 +1,13 @@ +package rlbotexample.manoeuvre; + +import rlbotexample.manoeuvre.sequence.ControlStep; +import rlbotexample.manoeuvre.sequence.Sequence; +import rlbotexample.output.Controls; + +public class Jump extends Sequence { + + public Jump(float holdTime, float idleTime) { + super(new ControlStep(holdTime, new Controls().withJump()), new ControlStep(idleTime, new Controls())); + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/Manoeuvre.java b/src/main/java/rlbotexample/manoeuvre/Manoeuvre.java new file mode 100644 index 0000000..462e0e6 --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/Manoeuvre.java @@ -0,0 +1,20 @@ +package rlbotexample.manoeuvre; + +import rlbotexample.input.DataPacket; +import rlbotexample.output.Controls; + +public abstract class Manoeuvre { + + private boolean finished; + + public abstract Controls tick(DataPacket packet); + + public boolean isFinished() { + return finished; + } + + public void setFinished(boolean finished) { + this.finished = finished; + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/Recovery.java b/src/main/java/rlbotexample/manoeuvre/Recovery.java new file mode 100644 index 0000000..216b3dd --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/Recovery.java @@ -0,0 +1,20 @@ +package rlbotexample.manoeuvre; + +import rlbotexample.input.DataPacket; +import rlbotexample.input.car.CarData; +import rlbotexample.output.Controls; +import rlbotexample.vector.Vector3; + +public class Recovery extends AerialTurn { + + public Recovery(CarData car) { + super(new Vector3(car.velocity.x, car.velocity.y, 0)); + } + + public Controls tick(DataPacket packet) { + Controls controls = super.tick(packet); + this.setFinished(packet.car.hasWheelContact); + return controls; + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/sequence/ControlStep.java b/src/main/java/rlbotexample/manoeuvre/sequence/ControlStep.java new file mode 100644 index 0000000..8f26058 --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/sequence/ControlStep.java @@ -0,0 +1,30 @@ +package rlbotexample.manoeuvre.sequence; + +import rlbotexample.input.DataPacket; +import rlbotexample.output.Controls; + +public class ControlStep extends Step { + + private float duration, startTime = -1; + private Controls controls; + + public ControlStep(float duration, Controls controls) { + super(); + this.duration = duration; + this.controls = controls; + } + + public ControlStep(double duration, Controls controls) { + this((float)duration, controls); + } + + @Override + public StepResult tick(DataPacket packet) { + if(this.startTime == -1) { + this.startTime = packet.elapsedSeconds; + } + float elapsedTime = packet.elapsedSeconds - this.startTime; + return new StepResult(this.controls, elapsedTime > this.duration); + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/sequence/Sequence.java b/src/main/java/rlbotexample/manoeuvre/sequence/Sequence.java new file mode 100644 index 0000000..448e2fe --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/sequence/Sequence.java @@ -0,0 +1,35 @@ +package rlbotexample.manoeuvre.sequence; + +import rlbotexample.input.DataPacket; +import rlbotexample.manoeuvre.Manoeuvre; +import rlbotexample.output.Controls; + +public class Sequence extends Manoeuvre { + + private Step[] steps; + private int index; + + public Sequence(Step... steps) { + super(); + this.steps = steps; + } + + public Controls tick(DataPacket packet) { + while(true) { + if(this.index >= this.steps.length) { + this.setFinished(true); + return new Controls(); + } + Step step = this.steps[this.index]; + StepResult result = step.tick(packet); + if(result.done) { + this.index ++; + if(this.index >= this.steps.length) { + this.setFinished(true); + } + } + return result.controls; + } + } + +} diff --git a/src/main/java/rlbotexample/manoeuvre/sequence/Step.java b/src/main/java/rlbotexample/manoeuvre/sequence/Step.java new file mode 100644 index 0000000..37a7649 --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/sequence/Step.java @@ -0,0 +1,9 @@ +package rlbotexample.manoeuvre.sequence; + +import rlbotexample.input.DataPacket; + +public abstract class Step { + + public abstract StepResult tick(DataPacket packet); + +} diff --git a/src/main/java/rlbotexample/manoeuvre/sequence/StepResult.java b/src/main/java/rlbotexample/manoeuvre/sequence/StepResult.java new file mode 100644 index 0000000..c17b91b --- /dev/null +++ b/src/main/java/rlbotexample/manoeuvre/sequence/StepResult.java @@ -0,0 +1,16 @@ +package rlbotexample.manoeuvre.sequence; + +import rlbotexample.output.Controls; + +public class StepResult { + + public Controls controls; + public boolean done; + + public StepResult(Controls controls, boolean done) { + super(); + this.controls = controls; + this.done = done; + } + +} diff --git a/src/main/java/rlbotexample/output/ControlsOutput.java b/src/main/java/rlbotexample/output/Controls.java similarity index 73% rename from src/main/java/rlbotexample/output/ControlsOutput.java rename to src/main/java/rlbotexample/output/Controls.java index 9cdbd2b..a358a62 100644 --- a/src/main/java/rlbotexample/output/ControlsOutput.java +++ b/src/main/java/rlbotexample/output/Controls.java @@ -8,7 +8,7 @@ * This class is here for your convenience, it is NOT part of the framework. You can change it as much * as you want, or delete it. */ -public class ControlsOutput implements ControllerState { +public class Controls implements ControllerState { // 0 is straight, -1 is hard left, 1 is hard right. private float steer; @@ -30,76 +30,76 @@ public class ControlsOutput implements ControllerState { private boolean slideDepressed; private boolean useItemDepressed; - public ControlsOutput() { + public Controls() { } - public ControlsOutput withSteer(float steer) { + public Controls withSteer(double steer) { this.steer = clamp(steer); return this; } - public ControlsOutput withPitch(float pitch) { + public Controls withPitch(double pitch) { this.pitch = clamp(pitch); return this; } - public ControlsOutput withYaw(float yaw) { + public Controls withYaw(double yaw) { this.yaw = clamp(yaw); return this; } - public ControlsOutput withRoll(float roll) { + public Controls withRoll(double roll) { this.roll = clamp(roll); return this; } - public ControlsOutput withThrottle(float throttle) { + public Controls withThrottle(double throttle) { this.throttle = clamp(throttle); return this; } - public ControlsOutput withJump(boolean jumpDepressed) { + public Controls withJump(boolean jumpDepressed) { this.jumpDepressed = jumpDepressed; return this; } - public ControlsOutput withBoost(boolean boostDepressed) { + public Controls withBoost(boolean boostDepressed) { this.boostDepressed = boostDepressed; return this; } - public ControlsOutput withSlide(boolean slideDepressed) { + public Controls withSlide(boolean slideDepressed) { this.slideDepressed = slideDepressed; return this; } - public ControlsOutput withUseItem(boolean useItemDepressed) { + public Controls withUseItem(boolean useItemDepressed) { this.useItemDepressed = useItemDepressed; return this; } - public ControlsOutput withJump() { + public Controls withJump() { this.jumpDepressed = true; return this; } - public ControlsOutput withBoost() { + public Controls withBoost() { this.boostDepressed = true; return this; } - public ControlsOutput withSlide() { + public Controls withSlide() { this.slideDepressed = true; return this; } - public ControlsOutput withUseItem() { + public Controls withUseItem() { this.useItemDepressed = true; return this; } - private float clamp(float value) { - return Math.max(-1, Math.min(1, value)); + private float clamp(double value) { + return Math.max(-1, Math.min(1, (float)value)); } @Override diff --git a/src/main/java/rlbotexample/prediction/BallPrediction.java b/src/main/java/rlbotexample/prediction/BallPrediction.java new file mode 100644 index 0000000..a335f2a --- /dev/null +++ b/src/main/java/rlbotexample/prediction/BallPrediction.java @@ -0,0 +1,34 @@ +package rlbotexample.prediction; + +import rlbot.cppinterop.RLBotDll; +import rlbot.cppinterop.RLBotInterfaceException; +import rlbot.flat.Physics; +import rlbotexample.prediction.slice.PhysicsSlice; +import rlbotexample.vector.Vector3; + +public class BallPrediction { + + public static final int MAX_SLICES = 360; + + public static PhysicsSlice[] slices = new PhysicsSlice[MAX_SLICES]; + + public static void update() { + try { + rlbot.flat.BallPrediction ballPrediction = RLBotDll.getBallPrediction(); + + for (int i = 0; i < MAX_SLICES; i++) { + if (slices[i] == null) { + slices[i] = new PhysicsSlice(); + } + Physics physics = ballPrediction.slices(i).physics(); + float elapsedSeconds = ballPrediction.slices(i).gameSeconds(); + slices[i].setPosition(new Vector3(physics.location())); + slices[i].setVelocity(new Vector3(physics.velocity())); + slices[i].setElapsedSeconds(elapsedSeconds); + } + } catch (RLBotInterfaceException e) { + e.printStackTrace(); + } + } + +} diff --git a/src/main/java/rlbotexample/prediction/BallPredictionHelper.java b/src/main/java/rlbotexample/prediction/BallPredictionHelper.java index 77b56c7..97327c2 100644 --- a/src/main/java/rlbotexample/prediction/BallPredictionHelper.java +++ b/src/main/java/rlbotexample/prediction/BallPredictionHelper.java @@ -1,30 +1,40 @@ package rlbotexample.prediction; -import rlbot.flat.BallPrediction; -import rlbot.flat.PredictionSlice; +import java.awt.Color; + import rlbot.render.Renderer; +import rlbotexample.prediction.slice.PhysicsSlice; +import rlbotexample.prediction.slice.PositionSlice; import rlbotexample.vector.Vector3; -import java.awt.*; - /** - * This class can help you get started with ball prediction. Feel free to change it as much as you want, - * this is part of your bot, not part of the framework! + * This class can help you get started with ball prediction. Feel free to change + * it as much as you want, this is part of your bot, not part of the framework! */ public class BallPredictionHelper { - public static void drawTillMoment(BallPrediction ballPrediction, float gameSeconds, Color color, Renderer renderer) { - Vector3 previousLocation = null; - for (int i = 0; i < ballPrediction.slicesLength(); i += 4) { - PredictionSlice slice = ballPrediction.slices(i); - if (slice.gameSeconds() > gameSeconds) { - break; - } - Vector3 location = new Vector3(slice.physics().location()); - if (previousLocation != null) { - renderer.drawLine3d(color, previousLocation, location); - } - previousLocation = location; - } - } + public static void drawTillMoment(float gameSeconds, Color color, Renderer renderer) { + Vector3 previousPosition = null; + for (int i = 0; i < BallPrediction.MAX_SLICES; i += 4) { + PhysicsSlice slice = BallPrediction.slices[i]; + if (slice.getElapsedSeconds() > gameSeconds) { + break; + } + Vector3 position = slice.getPosition(); + if (previousPosition != null) { + renderer.drawLine3d(color, previousPosition, position); + } + previousPosition = position; + } + } + + public static PositionSlice findFutureGoal() { + for (PhysicsSlice slice : BallPrediction.slices) { + if (Math.abs(slice.getPosition().y) > 5235) { + return new PositionSlice(slice.getPosition(), slice.getElapsedSeconds()); + } + } + return null; + } + } diff --git a/src/main/java/rlbotexample/prediction/slice/PhysicsSlice.java b/src/main/java/rlbotexample/prediction/slice/PhysicsSlice.java new file mode 100644 index 0000000..7cda697 --- /dev/null +++ b/src/main/java/rlbotexample/prediction/slice/PhysicsSlice.java @@ -0,0 +1,26 @@ +package rlbotexample.prediction.slice; + +import rlbotexample.vector.Vector3; + +public class PhysicsSlice extends PositionSlice { + + private Vector3 velocity; + + public PhysicsSlice(Vector3 position, Vector3 velocity, float elapsedSeconds) { + super(position, elapsedSeconds); + this.velocity = velocity; + } + + public PhysicsSlice() { + this(new Vector3(), new Vector3(), 0); + } + + public Vector3 getVelocity() { + return velocity; + } + + public void setVelocity(Vector3 velocity) { + this.velocity = velocity; + } + +} diff --git a/src/main/java/rlbotexample/prediction/slice/PositionSlice.java b/src/main/java/rlbotexample/prediction/slice/PositionSlice.java new file mode 100644 index 0000000..4ff05b4 --- /dev/null +++ b/src/main/java/rlbotexample/prediction/slice/PositionSlice.java @@ -0,0 +1,36 @@ +package rlbotexample.prediction.slice; + +import rlbotexample.vector.Vector3; + +public class PositionSlice { + + private Vector3 position; + private float elapsedSeconds; + + public PositionSlice(Vector3 position, float elapsedSeconds) { + super(); + this.position = position; + this.elapsedSeconds = elapsedSeconds; + } + + public PositionSlice() { + this(new Vector3(), 0); + } + + public Vector3 getPosition() { + return position; + } + + public float getElapsedSeconds() { + return elapsedSeconds; + } + + public void setPosition(Vector3 position) { + this.position = position; + } + + public void setElapsedSeconds(float elapsedSeconds) { + this.elapsedSeconds = elapsedSeconds; + } + +} diff --git a/src/main/java/rlbotexample/util/MathUtils.java b/src/main/java/rlbotexample/util/MathUtils.java new file mode 100644 index 0000000..2d351b7 --- /dev/null +++ b/src/main/java/rlbotexample/util/MathUtils.java @@ -0,0 +1,30 @@ +package rlbotexample.util; + +import rlbotexample.input.car.CarData; +import rlbotexample.input.car.CarOrientation; +import rlbotexample.vector.Vector3; + +public class MathUtils { + + public static float round(float value, int places) { + float ten = (float) Math.pow(10, places); + return Math.round(value * 10) / ten; + } + + public static float clamp11(float value) { + return clamp(value, -1, 1); + } + + private static float clamp(float value, int bound1, int bound2) { + return Math.max(Math.min(bound1, bound2), Math.min(value, Math.max(bound1, bound2))); + } + + public static Vector3 toLocal(CarData car, Vector3 vector) { + return toLocal(car.orientation, vector.minus(car.position)); + } + + public static Vector3 toLocal(CarOrientation orientation, Vector3 relative) { + return new Vector3(relative.dot(orientation.forward), relative.dot(orientation.right), relative.dot(orientation.up)); + } + +} diff --git a/src/main/java/rlbotexample/util/Tuple.java b/src/main/java/rlbotexample/util/Tuple.java new file mode 100644 index 0000000..85960c6 --- /dev/null +++ b/src/main/java/rlbotexample/util/Tuple.java @@ -0,0 +1,45 @@ +package rlbotexample.util; + +import java.io.Serializable; +import java.util.Objects; + +public class Tuple implements Serializable { + + private K key; + private V value; + + public Tuple(K key, V value) { + this.key = key; + this.value = value; + } + + public K getKey() { + return key; + } + + public V getValue() { + return value; + } + + @Override + public String toString() { + return key + "=" + value; + } + + @Override + public int hashCode() { + return key.hashCode() * 13 + (value == null ? 0 : value.hashCode()); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o instanceof Tuple) { + Tuple pair = (Tuple) o; + if (!Objects.equals(key, pair.key)) return false; + if (!Objects.equals(value, pair.value)) return false; + return true; + } + return false; + } +} \ No newline at end of file diff --git a/src/main/java/rlbotexample/vector/Matrix3x3.java b/src/main/java/rlbotexample/vector/Matrix3x3.java new file mode 100644 index 0000000..348008c --- /dev/null +++ b/src/main/java/rlbotexample/vector/Matrix3x3.java @@ -0,0 +1,358 @@ +package rlbotexample.vector; + +import java.util.Arrays; + +public class Matrix3x3 { + private final float[] data = new float[9]; + + public Matrix3x3() { + Arrays.fill(data, 0); + } + + public Matrix3x3(Matrix3x3 other) { + System.arraycopy(other.data, 0, this.data, 0, data.length); + } + + + public static Matrix3x3 identity() { + Matrix3x3 mat = new Matrix3x3(); + mat.assign(0, 0, 1); + mat.assign(1, 1, 1); + mat.assign(2, 2, 1); + return mat; + } + + + public static Matrix3x3 antiSym( Vector3 w) { + // http://mathworld.wolfram.com/AntisymmetricMatrix.html + + Matrix3x3 mat = new Matrix3x3(); + mat.assign(0, 1, -w.z); + mat.assign(1, 0, w.z); + + mat.assign(2, 0, -w.y); + mat.assign(0, 2, w.y); + + mat.assign(1, 2, -w.x); + mat.assign(2, 1, w.x); + return mat; + } + + + public static Matrix3x3 R3_basis(Vector3 n) { + float sign = n.z >= 0f ? 1f : -1f; + float a = -1f / (sign + n.z); + float b = n.x * n.y * a; + + Matrix3x3 mat = new Matrix3x3(); + + mat.assign(0, 0, 1f + sign * n.x * n.x * a); + mat.assign(0, 1, b); + mat.assign(0, 2, n.x); + + mat.assign(1, 0, sign * b); + mat.assign(1, 1, sign + n.y * n.y * a); + mat.assign(1, 2, n.y); + + mat.assign(2, 0, -sign * n.x); + mat.assign(2, 1, -n.y); + mat.assign(2, 2, n.z); + + return mat; + } + + + public static Matrix3x3 axisToRotation(Vector3 omega) { + float norm_omega = (float) omega.magnitude(); + + if (Math.abs(norm_omega) == 0) + norm_omega = 1.1755e-38f; + { + Vector3 u = omega.div(norm_omega); + + float c = (float) Math.cos(norm_omega); + float s = (float) Math.sin(norm_omega); + + Matrix3x3 mat = new Matrix3x3(); + + mat.assign(0, 0, u.get(0) * u.get(0) * (1.0f - c) + c); + mat.assign(0, 1, u.get(0) * u.get(1) * (1.0f - c) - u.get(2) * s); + mat.assign(0, 2, u.get(0) * u.get(2) * (1.0f - c) + u.get(1) * s); + + mat.assign(1, 0, u.get(1) * u.get(0) * (1.0f - c) + u.get(2) * s); + mat.assign(1, 1, u.get(1) * u.get(1) * (1.0f - c) + c); + mat.assign(1, 2, u.get(1) * u.get(2) * (1.0f - c) - u.get(0) * s); + + mat.assign(2, 0, u.get(2) * u.get(0) * (1.0f - c) - u.get(1) * s); + mat.assign(2, 1, u.get(2) * u.get(1) * (1.0f - c) + u.get(0) * s); + mat.assign(2, 2, u.get(2) * u.get(2) * (1.0f - c) + c); + + return mat; + } + } + + + public static Matrix3x3 eulerToRotation(Vector3 pyr) { + Matrix3x3 mat = new Matrix3x3(); + float CP = (float) Math.cos(pyr.x); + float SP = (float) Math.sin(pyr.x); + float CY = (float) Math.cos(pyr.y); + float SY = (float) Math.sin(pyr.y); + float CR = (float) Math.cos(pyr.z); + float SR = (float) Math.sin(pyr.z); + + mat.assign(0, 0, CP * CY); + mat.assign(0, 0, CP * CY); + mat.assign(1, 0, CP * SY); + mat.assign(2, 0, SP); + + mat.assign(0, 1, CY * SP * SR - CR * SY); + mat.assign(1, 1, SY * SP * SR + CR * CY); + mat.assign(2, 1, -CP * SR); + + mat.assign(0, 2, -CR * CY * SP - SR * SY); + mat.assign(1, 2, -CR * SY * SP + SR * CY); + mat.assign(2, 2, CP * CR); + + return mat; + } + + + public static Matrix3x3 from( Vector3 forward, Vector3 up, Vector3 left) { + Matrix3x3 mat = new Matrix3x3(); + + mat.assign(0, 0, forward.x); + mat.assign(1, 0, forward.y); + mat.assign(2, 0, forward.z); + + mat.assign(0, 1, left.x); + mat.assign(1, 1, left.y); + mat.assign(2, 1, left.z); + + mat.assign(0, 2, up.x); + mat.assign(1, 2, up.y); + mat.assign(2, 2, up.z); + + return mat; + } + + + public static Matrix3x3 lookAt(Vector3 direction, Vector3 up) { + if (up == null) + up = new Vector3(0, 0, 1); + + Vector3 f = direction.normalized(); + Vector3 u = f.cross(up.cross(f)).normalized(); + Vector3 l = u.cross(f).normalized(); + + Matrix3x3 mat = new Matrix3x3(); + mat.assign(0, 0, f.x); + mat.assign(0, 1, l.x); + mat.assign(0, 2, u.x); + + mat.assign(1, 0, f.y); + mat.assign(1, 1, l.y); + mat.assign(1, 2, u.y); + + mat.assign(2, 0, f.z); + mat.assign(2, 1, l.z); + mat.assign(2, 2, u.z); + return mat; + } + + + public static Matrix3x3 roofTo(Vector3 up, Vector3 generalDirection) { + Vector3 f = new Vector3(); + + if (generalDirection != null) { + // https://stackoverflow.com/a/9605695 + + double dist = generalDirection.dot(up); + Vector3 projected = generalDirection.sub(up.mul(dist)); + f = projected.normalized(); + } + + if (f.isZero()) + f = new Vector3(0, 0, -1); + Vector3 u = f.cross(up.cross(f)).normalized(); + Vector3 l = u.cross(f).normalized(); + + Matrix3x3 mat = new Matrix3x3(); + mat.assign(0, 0, f.x); + mat.assign(0, 1, l.x); + mat.assign(0, 2, u.x); + + mat.assign(1, 0, f.y); + mat.assign(1, 1, l.y); + mat.assign(1, 2, u.y); + + mat.assign(2, 0, f.z); + mat.assign(2, 1, l.z); + mat.assign(2, 2, u.z); + return mat; + } + + public float[][] getFloatMatrix() { + return new float[][]{ + {get(0, 0), get(0, 1), get(0, 2)}, + {get(1, 0), get(1, 1), get(1, 2)}, + {get(2, 0), get(2, 1), get(2, 2)} + }; + } + + public Vector3 toEuler() { + return new Vector3( + (float) Math.atan2(this.get(2, 0), new Vector2(this.get(0, 0), this.get(1, 0)).magnitude()), + (float) Math.atan2(this.get(1, 0), this.get(0, 0)), + (float) Math.atan2(-this.get(2, 1), this.get(2, 2)) + ); + } + + public Vector3 up() { + return new Vector3(this.get(0, 2), this.get(1, 2), this.get(2, 2)); + } + + public Vector3 forward() { + return new Vector3(this.get(0, 0), this.get(1, 0), this.get(2, 0)); + } + + public Vector3 right() { + return new Vector3(this.get(0, 1), this.get(1, 1), this.get(2, 1)); + } + + public void assign(int row, int column, float value) { + this.data[row + column * 3] = value; + } + + public float get(int row, int column) { + return this.data[row + column * 3]; + } + + public float det() { + return + +this.get(0, 0) * this.get(1, 1) * this.get(2, 2) + + this.get(0, 1) * this.get(1, 2) * this.get(2, 0) + + this.get(0, 2) * this.get(1, 0) * this.get(2, 1) + - this.get(0, 0) * this.get(1, 2) * this.get(2, 1) + - this.get(0, 1) * this.get(1, 0) * this.get(2, 2) + - this.get(0, 2) * this.get(1, 1) * this.get(2, 0); + } + + public float tr() { + float sum = 0; + for (int i = 0; i < 3; i++) + sum += get(i, i); + return sum; + } + + public Matrix3x3 invert() { + Matrix3x3 invA = new Matrix3x3(); + + float inv_detA = 1.0f / this.det(); + + invA.assign(0, 0, (this.get(1, 1) * this.get(2, 2) - this.get(1, 2) * this.get(2, 1)) * inv_detA); + invA.assign(0, 1, (this.get(0, 2) * this.get(2, 1) - this.get(0, 1) * this.get(2, 2)) * inv_detA); + invA.assign(0, 2, (this.get(0, 1) * this.get(1, 2) - this.get(0, 2) * this.get(1, 1)) * inv_detA); + invA.assign(1, 0, (this.get(1, 2) * this.get(2, 0) - this.get(1, 0) * this.get(2, 2)) * inv_detA); + invA.assign(1, 1, (this.get(0, 0) * this.get(2, 2) - this.get(0, 2) * this.get(2, 0)) * inv_detA); + invA.assign(1, 2, (this.get(0, 2) * this.get(1, 0) - this.get(0, 0) * this.get(1, 2)) * inv_detA); + invA.assign(2, 0, (this.get(1, 0) * this.get(2, 1) - this.get(1, 1) * this.get(2, 0)) * inv_detA); + invA.assign(2, 1, (this.get(0, 1) * this.get(2, 0) - this.get(0, 0) * this.get(2, 1)) * inv_detA); + invA.assign(2, 2, (this.get(0, 0) * this.get(1, 1) - this.get(0, 1) * this.get(1, 0)) * inv_detA); + + return invA; + } + + public Matrix3x3 div(float denominator) { + Matrix3x3 B = new Matrix3x3(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + B.assign(i, j, this.get(i, j) / denominator); + } + } + return B; + } + + public Matrix3x3 elementwiseMul(float denominator) { + Matrix3x3 B = new Matrix3x3(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + B.assign(i, j, this.get(i, j) * denominator); + } + } + return B; + } + + public Matrix3x3 add(Matrix3x3 other) { + Matrix3x3 B = new Matrix3x3(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + B.assign(i, j, this.get(i, j) + other.get(i, j)); + } + } + return B; + } + + public Matrix3x3 sub(Matrix3x3 other) { + Matrix3x3 B = new Matrix3x3(); + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + B.assign(i, j, this.get(i, j) - other.get(i, j)); + } + } + return B; + } + + public Matrix3x3 transpose() { + Matrix3x3 other = new Matrix3x3(); + + for (int r = 0; r < 3; r++) { + for (int c = 0; c < 3; c++) { + other.assign(r, c, get(c, r)); + } + } + + return other; + } + + public Vector3 dot(Vector3 other) { + float[] vecArr = new float[3]; + for (int i = 0; i < 3; i++) { + vecArr[i] = 0; + for (int j = 0; j < 3; j++) { + vecArr[i] += get(i, j) * other.get(j); + } + } + return new Vector3(vecArr[0], vecArr[1], vecArr[2]); + } + + public Matrix3x3 matrixMul(Matrix3x3 other) { + Matrix3x3 C = new Matrix3x3(); + + for (int i = 0; i < 3; i++) { + for (int j = 0; j < 3; j++) { + C.assign(i, j, 0.0f); + for (int k = 0; k < 3; k++) { + C.assign(i, j, (C.get(i, j) + this.get(i, k) * other.get(k, j))); + } + } + } + + return C; + } + + public float angle( Matrix3x3 other) { + return (float) Math.acos(0.5f * (this.matrixMul(other.transpose()).tr() - 1.0f)); + } + + @Override + public String toString() { + float[][] dat = getFloatMatrix(); + final StringBuilder sb = new StringBuilder("Matrix3x3[\n"); + sb.append(Arrays.toString(dat[0])).append(",\n"); + sb.append(Arrays.toString(dat[1])).append(",\n"); + sb.append(Arrays.toString(dat[2])).append("\n"); + sb.append(']'); + return sb.toString(); + } +} diff --git a/src/main/java/rlbotexample/vector/Vector2.java b/src/main/java/rlbotexample/vector/Vector2.java index dad1633..f30afa7 100644 --- a/src/main/java/rlbotexample/vector/Vector2.java +++ b/src/main/java/rlbotexample/vector/Vector2.java @@ -8,12 +8,15 @@ */ public class Vector2 { - public final double x; - public final double y; + public final float x; + public final float y; + + public static final Vector2 X = new Vector2(1, 0); + public static final Vector2 Y = new Vector2(0, 1); public Vector2(double x, double y) { - this.x = x; - this.y = y; + this.x = (float) x; + this.y = (float) y; } public Vector2 plus(Vector2 other) { diff --git a/src/main/java/rlbotexample/vector/Vector3.java b/src/main/java/rlbotexample/vector/Vector3.java index 506ab21..f622113 100644 --- a/src/main/java/rlbotexample/vector/Vector3.java +++ b/src/main/java/rlbotexample/vector/Vector3.java @@ -5,103 +5,173 @@ /** * A simple 3d vector class with the most essential operations. * - * This class is here for your convenience, it is NOT part of the framework. You can add to it as much - * as you want, or delete it. + * This class is here for your convenience, it is NOT part of the framework. You + * can add to it as much as you want, or delete it. */ public class Vector3 extends rlbot.vector.Vector3 { - public Vector3(double x, double y, double z) { - super((float) x, (float) y, (float) z); + public static final Vector3 X = new Vector3(1, 0, 0); + public static final Vector3 Y = new Vector3(0, 1, 0); + public static final Vector3 Z = new Vector3(0, 0, 1); + + public Vector3(double x, double y, double z) { + super((float) x, (float) y, (float) z); + } + + public Vector3() { + this(0, 0, 0); + } + + public Vector3(rlbot.flat.Vector3 vec) { + // Invert the X value so that the axes make more sense. + this(-vec.x(), vec.y(), vec.z()); + } + + public int toFlatbuffer(FlatBufferBuilder builder) { + // Invert the X value again so that rlbot sees the format it expects. + return rlbot.flat.Vector3.createVector3(builder, -x, y, z); + } + + public Vector3 plus(Vector3 other) { + return new Vector3(x + other.x, y + other.y, z + other.z); + } + + public Vector3 minus(Vector3 other) { + return new Vector3(x - other.x, y - other.y, z - other.z); + } + + public Vector3 scaled(double scale) { + return new Vector3(x * scale, y * scale, z * scale); + } + + public double dot(Vector3 other) { + return x * other.x + y * other.y + z * other.z; } - public Vector3() { - this(0, 0, 0); + public Vector3 add(Vector3 other) { + return new Vector3(x + other.x, y + other.y, z + other.z); } - public Vector3(rlbot.flat.Vector3 vec) { - // Invert the X value so that the axes make more sense. - this(-vec.x(), vec.y(), vec.z()); + public Vector3 add(float other) { + return new Vector3(x + other, y + other, z + other); } - public int toFlatbuffer(FlatBufferBuilder builder) { - // Invert the X value again so that rlbot sees the format it expects. - return rlbot.flat.Vector3.createVector3(builder, -x, y, z); + public Vector3 add(Vector2 other, float z) { + return new Vector3(x + other.x, y + other.y, this.z + z); } - public Vector3 plus(Vector3 other) { - return new Vector3(x + other.x, y + other.y, z + other.z); + public Vector3 add(float x, float y, float z) { + return new Vector3(this.x + x, this.y + y, this.z + z); } - public Vector3 minus(Vector3 other) { + public Vector3 sub(Vector3 other) { return new Vector3(x - other.x, y - other.y, z - other.z); } - public Vector3 scaled(double scale) { - return new Vector3(x * scale, y * scale, z * scale); + public Vector3 sub(float xS, float yS, float zS) { + return new Vector3(x - xS, y - yS, z - zS); } - /** - * If magnitude is negative, we will return a vector facing the opposite direction. - */ - public Vector3 scaledToMagnitude(double magnitude) { - if (isZero()) { - throw new IllegalStateException("Cannot scale up a vector with length zero!"); - } - double scaleRequired = magnitude / magnitude(); - return scaled(scaleRequired); + public Vector3 sub(float other) { + return new Vector3(x - other, y - other, z - other); } - public double distance(Vector3 other) { - double xDiff = x - other.x; - double yDiff = y - other.y; - double zDiff = z - other.z; - return Math.sqrt(xDiff * xDiff + yDiff * yDiff + zDiff * zDiff); + public Vector3 mul(double scale) { + return new Vector3(x * scale, y * scale, z * scale); } - public double magnitude() { - return Math.sqrt(magnitudeSquared()); + public Vector3 mul(Vector3 other) { + return new Vector3(x * other.x, y * other.y, z * other.z); } - public double magnitudeSquared() { - return x * x + y * y + z * z; + public Vector3 mul(float xS, float yS, float zS) { + return new Vector3(x * xS, y * yS, z * zS); } - public Vector3 normalized() { - if (isZero()) { - throw new IllegalStateException("Cannot normalize a vector with length zero!"); + public Vector3 dot(Matrix3x3 o) { + float[] vA = new float[3]; + for (int i = 0; i < 3; i++) { + vA[i] = 0; + for (int j = 0; j < 3; j++) { + vA[i] += this.get(j) * o.get(j, i); + } } - return this.scaled(1 / magnitude()); + return new Vector3(vA[0], vA[1], vA[2]); } - public double dotProduct(Vector3 other) { - return x * other.x + y * other.y + z * other.z; - } - - public boolean isZero() { - return x == 0 && y == 0 && z == 0; + public float get(int index) { + if (index == 0) + return x; + if (index == 1) + return y; + if (index == 2) + return z; + return 0; } - public Vector2 flatten() { - return new Vector2(x, y); - } + /** + * If magnitude is negative, we will return a vector facing the opposite* direction. + */ + public Vector3 scaledToMagnitude(double magnitude) { + if (isZero()) { + throw new IllegalStateException("Cannot scale up a vector with length zero!"); + } + double scaleRequired = magnitude / magnitude(); + return scaled(scaleRequired); + } - public double angle(Vector3 v) { - double mag2 = magnitudeSquared(); - double vmag2 = v.magnitudeSquared(); - double dot = dotProduct(v); - return Math.acos(dot / Math.sqrt(mag2 * vmag2)); + public Vector3 div(double scale) { + return new Vector3(x / scale, y / scale, z / scale); } - public Vector3 crossProduct(Vector3 v) { - double tx = y * v.z - z * v.y; - double ty = z * v.x - x * v.z; - double tz = x * v.y - y * v.x; - return new Vector3(tx, ty, tz); + public double distance(Vector3 other) { + double xDiff = x - other.x; + double yDiff = y - other.y; + double zDiff = z - other.z; + return Math.sqrt(xDiff * xDiff + yDiff * yDiff + zDiff * zDiff); } - @Override - public String toString() { - return String.format("(%s, %s, %s)", x, y, z); - } + public double magnitude() { + return Math.sqrt(magnitudeSquared()); + } + + public double magnitudeSquared() { + return x * x + y * y + z * z; + } + + public Vector3 normalized() { + + if (isZero()) { + throw new IllegalStateException("Cannot normalize a vector with length zero!"); + } + return this.scaled(1 / magnitude()); + } + + public boolean isZero() { + return x == 0 && y == 0 && z == 0; + } + + public Vector2 flatten() { + return new Vector2(x, y); + } + + public double angle(Vector3 v) { + double mag2 = magnitudeSquared(); + double vmag2 = v.magnitudeSquared(); + double dot = dot(v); + return Math.acos(dot / Math.sqrt(mag2 * vmag2)); + } + + public Vector3 cross(Vector3 v) { + double tx = y * v.z - z * v.y; + double ty = z * v.x - x * v.z; + double tz = x * v.y - y * v.x; + return new Vector3(tx, ty, tz); + } + + @Override + public String toString() { + return String.format("(%s, %s, %s)", x, y, z); + } }