diff --git a/code/src/Fitbot/app/build.gradle b/code/src/Fitbot/app/build.gradle index 5148a3d..38cf8be 100644 --- a/code/src/Fitbot/app/build.gradle +++ b/code/src/Fitbot/app/build.gradle @@ -7,7 +7,7 @@ android { defaultConfig { applicationId "com.example.fitbot" - minSdk 23 + minSdk 29 targetSdk 29 versionCode 1 versionName "1.0" diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainScreen.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainScreen.java index 8b72054..bc3f9a2 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainScreen.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainScreen.java @@ -36,14 +36,14 @@ public class MainScreen extends AppCompatActivity { motionProcessor.parsePacket("sampleRate 1"); - Vector3[] path = new Vector3[100]; + GesturePath.Builder builder = new GesturePath.Builder(); for ( int i = 0; i < 100; i++ ) { - path[i] = new Vector3(0, Math.sin(i * Math.PI / 50), 0); - motionProcessor.parsePacket("data 0;" + Math.cos(i * Math.PI / 50) + ";0;0;0;0"); + builder.addVector(new Vector3(0, Math.sin(i * Math.PI / 50), 0)); + motionProcessor.parsePacket("data 0;" + (-Math.sin(i * Math.PI / 50)) + ";0;0;0;0"); } - motionProcessor.calculatePath(new GesturePath(path)); + motionProcessor.calculatePath(); motionProcessor.printData(); /*---Tool Bar---*/ diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java index db15312..a68d35b 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java @@ -1,28 +1,101 @@ package com.example.fitbot.util.processing; +import java.util.ArrayList; +import java.util.List; + public class GesturePath { public Vector3[] vectors; - public GesturePath(Vector3[] vectors) - { + public GesturePath(Vector3[] vectors) { this.vectors = vectors; } - public Vector3 getError(Vector3 compare) { - Vector3 error = new Vector3(0, 0, 0); - double distance, previous = Double.MAX_VALUE; + /** + * Get the error between a line segment and the reference point. + * + * @param linePointA The first point of the line. + * @param linePointB The second point of the line. + * @param referencePoint The reference point to calculate the error of. + * @return The error offset between the line and the reference point. + */ + public static double getError(Vector3 linePointA, Vector3 linePointB, Vector3 referencePoint) { + double lineDistance = linePointA.distanceSq(linePointB); + if (lineDistance == 0) + return referencePoint.distanceSq(linePointA); - // Calculate the minimum distance between the vectors. - // This is used to determine the error. - for (int i = 0; i < vectors.length; i++) { - distance = vectors[i].distance(compare); - if ( distance < previous ) { - error = vectors[i]; - previous = distance; + double t = ((referencePoint.x - linePointA.x) * (linePointB.x - linePointA.x) + + (referencePoint.y - linePointA.y) * (linePointB.y - linePointA.y) + + (referencePoint.z - linePointA.z) * (linePointB.z - linePointA.z)) / lineDistance; + + t = Math.max(0, Math.min(1, t)); + + return referencePoint.distanceSq(new Vector3( + linePointA.x + t * (linePointB.x - linePointA.x), + linePointA.y + t * (linePointB.y - linePointA.y), + linePointA.z + t * (linePointB.z - linePointA.z)) + ); + } + + /** + * Get the error between an arbitrary path segment and the reference point. + * + * @param referencePoint The reference point to calculate the error of. + * @return The error offset between the path and the reference point. + */ + public double getError(Vector3 referencePoint) { + double distance = Double.MAX_VALUE; + for (int i = 0; i < vectors.length - 1; i++) { + double error = getError(vectors[i], vectors[i + 1], referencePoint); + if (error < distance) { + distance = error; } } - return error; + return distance; + } + + // Builder class for the GesturePath object. + public static class Builder { + + // List of vectors to add to the GesturePath object. + private final List vectors; + + /** + * Constructor for the Builder object. + * + * @param vectors The list of vectors to add. + */ + public Builder(List vectors) { + this.vectors = vectors; + } + + /** + * Default constructor for the Builder object. + */ + public Builder() { + this.vectors = new ArrayList<>(); + } + + /** + * Adds a vector to the GesturePath object. + * + * @param vector The vector to add. + * @return The Builder object. + */ + public Builder addVector(Vector3 vector) { + vectors.add(vector); + return this; + } + + /** + * Builds the GesturePath object. + * + * @return The GesturePath object. + */ + public GesturePath build() { + return new GesturePath(vectors.toArray(new Vector3[0])); + } + } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java index 4c4ea0b..edd037b 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionData.java @@ -60,22 +60,4 @@ public class MotionData { Float.parseFloat(parts[5]) ); } - - /** - * Class for representing parsed motion data. - * This data is parsed in the `MotionProcessor` class. - */ - public static class ParsedVector { - public Vector3 relativePosition; - public Vector3 relativeError; - public ParsedVector(Vector3 relativePosition, Vector3 relativeError) { - this.relativePosition = relativePosition; - this.relativeError = relativeError; - } - - public String toString() { - return "position[" + relativePosition.toString() + "] error[" + relativeError.toString() + "]"; - } - } - } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java index aac3d09..ecf5303 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/MotionProcessor.java @@ -9,15 +9,14 @@ import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; public class MotionProcessor { - private static final int MAX_PATH_LENGTH = 100; public static final String DELIMITER = ";"; private final List motionData = new ArrayList<>(); - private final List parsedData = new ArrayList<>(); - private final Vector3[] relativePath = new Vector3[MAX_PATH_LENGTH]; + private final List relativePath = new ArrayList<>(); // Relative path of the motion data private Vector3 ZERO = new Vector3(0, 0, 0); private double sampleRate = 1.0D; // samples/second @@ -76,33 +75,91 @@ public class MotionProcessor { } /** - * Function for calculating the path of the device. - * This only works when the ZERO point has been provided. + * Function for adding motion data to the processor. + * + * @param data The motion data to add. */ - public void calculatePath(GesturePath path) { + public void addMotionData(MotionData data) { + this.motionData.add(data); + } + + /** + * Function for calculating the 'absolute' path of the motion data. + * This converts the relative acceleration and rotation vectors and converts + * them to a position that is relative to the ZERO point. + */ + public void calculatePath() { + + relativePath.clear(); // Clear previous path - int offset = Math.max(0, motionData.size() - MAX_PATH_LENGTH); Vector3 previous = ZERO.copy(); - for (int i = offset; i < motionData.size(); i++) { + for (int i = 0; i < motionData.size(); i++) { MotionData data = motionData.get(i); - Vector3 relativePosition = data.acceleration - .rotate(data.rotation.negate()) // Rotate acceleration vector with it's negated angle to get the acceleration relative to the vector of gravity - .multiply(sampleRate * sampleRate / 6.0D) // Apply double integration to get the relative position - .add(previous); // Add the previous position to get the new position + Vector3 relativeVector = getRelativeVector(data) + .add(previous); - Vector3 relativeError = path.getError(relativePosition); - - previous = relativePosition; - - // Get relative vector from the ZERO point - parsedData.add(new MotionData.ParsedVector(relativePosition, relativeError)); + relativePath.add(relativeVector); + previous = relativeVector; } } + + /** + * Function for getting the relative vector of the motion data. + * This function will calculate the relative position of the motion data + * based on its acceleration and rotation vectors. This has to be done since + * the acceleration vector is relative to its own rotation vector. + * + * @param motionData The motion data to calculate the relative vector for. + * @return The relative vector of the motion data. + */ + public Vector3 getRelativeVector(MotionData motionData) { + + // Rotate the acceleration vector back by the rotation vector to make it + // perpendicular to the gravity vector, then apply double integration to get the relative position. + // s = 1/2 * a * t^2 + return motionData.acceleration + .rotate(motionData.rotation.negate()) + .multiply(sampleRate * sampleRate / 6.0D); + } + + /** + * Function for getting the error offsets of the provided path and the + * received motion data. + * + * @param referencePath The reference path to compare the motion data to. + * @return A list of error offsets of the motion data compared to the reference path. + */ + public List getErrors(GesturePath referencePath) { + + // If the relative path is empty, calculate it. + if ( relativePath.isEmpty()) + calculatePath(); + + // Return the errors of the relative path compared to the reference path. + return relativePath + .stream() + .map(referencePath::getError) + .collect(Collectors.toList()); + } + + /** + * Function for calculating the average error of the motion data + * compared to the reference path. + * + * @param referencePath The reference path to compare the motion data to. + * @return The average error of the motion data compared to the reference path. + */ + public double averageError(GesturePath referencePath) { + return getErrors(referencePath) + .stream() + .mapToDouble(Double::doubleValue) + .average() + .orElse(0.0D); + } + public void printData() { - for (MotionData.ParsedVector vector : parsedData) { - Log.i("MotionProcessor", vector.toString()); - } + } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java index 1a8b38b..0f86df5 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java @@ -175,6 +175,16 @@ public class Vector3 { ); } + /** + * Get the squared distance between this vector and another vector. + * + * @param compare The other vector. + * @return The squared distance between the two vectors. + */ + public double distanceSq(Vector3 compare) { + return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2); + } + /** * Get the distance between this vector and another vector. * @@ -182,7 +192,7 @@ public class Vector3 { * @return The distance between the two vectors. */ public double distance(Vector3 compare) { - return Math.sqrt(Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2)); + return Math.sqrt(distanceSq(compare)); } public Vector3 map(VectorMapFunction function) {