From 11489682eb38adf427947eb2d8a8629f2a80ed94 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 01:10:51 +0200 Subject: [PATCH 01/11] Updated path math, started with 3d path to 2d screen space projection --- code/src/Fitbot/app/build.gradle | 1 + .../Fitbot/app/src/main/AndroidManifest.xml | 4 - .../java/com/example/fitbot/MainScreen.java | 4 - .../PersonalMotionPreviewElement.java | 132 +++++++++++-- .../example/fitbot/util/path/GesturePath.java | 124 +++++++++++++ .../example/fitbot/util/path/PathSegment.java | 175 ++++++++++++++++++ .../Vector3.java => path/Point3D.java} | 70 +++---- .../fitbot/util/processing/GesturePath.java | 104 ----------- .../fitbot/util/processing/MotionData.java | 24 +-- .../util/processing/MotionProcessor.java | 35 ++-- 10 files changed, 488 insertions(+), 185 deletions(-) create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java rename code/src/Fitbot/app/src/main/java/com/example/fitbot/util/{processing/Vector3.java => path/Point3D.java} (78%) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java diff --git a/code/src/Fitbot/app/build.gradle b/code/src/Fitbot/app/build.gradle index 38cf8be..f3a2fea 100644 --- a/code/src/Fitbot/app/build.gradle +++ b/code/src/Fitbot/app/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:2.0.4' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0' + implementation 'org.joml:joml:1.10.5' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/code/src/Fitbot/app/src/main/AndroidManifest.xml b/code/src/Fitbot/app/src/main/AndroidManifest.xml index bd9a6b6..1705cd8 100644 --- a/code/src/Fitbot/app/src/main/AndroidManifest.xml +++ b/code/src/Fitbot/app/src/main/AndroidManifest.xml @@ -14,10 +14,6 @@ - - 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 f8ddf0a..7315545 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 @@ -9,10 +9,6 @@ import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; -import com.example.fitbot.util.processing.GesturePath; -import com.example.fitbot.util.processing.MotionProcessor; -import com.example.fitbot.util.processing.Vector3; - public class MainScreen extends AppCompatActivity { //Variables diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java index b5161d8..d70c88c 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java @@ -5,39 +5,147 @@ import android.graphics.Canvas; import android.graphics.Path; import android.view.View; -import com.example.fitbot.util.processing.GesturePath; +import com.example.fitbot.util.path.GesturePath; +import com.example.fitbot.util.path.PathSegment; +import com.example.fitbot.util.path.Point3D; import com.example.fitbot.util.processing.MotionData; import com.example.fitbot.util.processing.MotionProcessor; -import com.example.fitbot.util.processing.Vector3; + +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; public class PersonalMotionPreviewElement extends View { private GesturePath path; private double pathTime = 0.0D; // The timestamp at which the path is currently at. private MotionProcessor motionProcessor; - private Path targetPath, personalPath; + private Path referencePath, performingPath; /** - * Method that calculates the path that will be drawn on the - * canvas. This method will be called every time new motion data is received. + * Constants for the preview path projection. */ - private void calculateDrawingPath(Vector3 transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { - - } - + private final float FOV = 70.0f; // The field of view of the preview path + private final float Z_NEAR = 0.1f; // The near clipping plane + private final float Z_FAR = 1000.0f; // The far clipping plane + private Vector3f cameraPosition = new Vector3f(0.0f, 0.0f, 0.0f); // The position of the camera + private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen + private Vector2f rotation = new Vector2f(); // Rotation vector (yaw, pitch) + /** + * Constructor for the PersonalMotionPreviewElement class. + * + * @param context The context in which this element is created. + * @param path The gesture path that will be drawn on the canvas. + */ public PersonalMotionPreviewElement(Context context, GesturePath path) { super(context); this.path = path; this.motionProcessor = new MotionProcessor(); this.motionProcessor.startListening(); - this.motionProcessor.setMotionDataEventHandler(this::calculateDrawingPath); - this.targetPath = new Path(); - this.personalPath = new Path(); + this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate) -> { + // TODO: Implement the calculation of the drawing path + }); + + this.referencePath = generatePath() + this.performingPath = new Path(); } + /** + * Method that calculates the path that will be drawn on the + * canvas. This method will be called every time new motion data is received. + */ + private void calculateDrawingPath(Point3D transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { + // Recalculate the personal path based on the new motion data + + } + + /** + * Method for setting the rotation of the preview path. + * + * @param yaw The yaw rotation of the preview path. + * @param pitch The pitch rotation of the preview path. + */ + public void setRotation(float yaw, float pitch) { + this.rotation.set(Math.toRadians(yaw), Math.toRadians(pitch)); + } + + /** + * Method for projecting a 3D point onto the screen. + * This method converts the 3D point to 2D space using a Model-View-Projection matrix transformation. + * + * @param point The point to cast to the screen. + * @param virtualWidth The width of the virtual screen. + * This is used to normalize the screen coordinates. + * @param virtualHeight The height of the virtual screen. + * @return The transformed vector in screen coordinates ranging from (0, 0) to (virtualWidth, virtualHeight). + */ + private Vector2f projectVertex(Vector3f point, int virtualWidth, int virtualHeight) { + + Matrix4f modelViewMatrix = new Matrix4f() + .rotateX((float) Math.toRadians(rotation.x)) + .rotateY((float) Math.toRadians(rotation.y)) + .translate(cameraPosition); + + Matrix4f projectionMatrix = new Matrix4f() + .perspective((float) Math.toRadians(FOV), (float) virtualWidth / virtualHeight, Z_NEAR, Z_FAR); + + // Calculate Model-View-Projection matrix + Matrix4f MVP = new Matrix4f() + .set(projectionMatrix) + .mul(modelViewMatrix); + + // Convert to screen coordinates + Vector4f screenCoordinates = new Vector4f(point, 1.0f) + .mul(MVP); + + // Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight) + float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth; + float normalizedY = (1.0f - screenCoordinates.y / screenCoordinates.w) * 0.5f * virtualHeight; + + return new Vector2f(normalizedX, normalizedY); + } + + /** + * Method that converts a sequence of vectors to a Path object. + * This path is a set of bezier curves that will be drawn on the canvas. + * + * @param curvature The curvature of the bezier curves. + * This number must be between 0 and 1, and it represents + * by how much the path segments will be curved. + * A value of 0 represents no curvature at all, + * while values closer to 1 approach full circular curvature. + * @param segments The path segments in the path. + * These segments will be connected by bezier curves, which + * all have unique curvature values. + * @return The generated path object. + */ + private Path getDrawablePath(double curvature, PathSegment... segments) { + + Path calculatedPath = new Path(); + + // Starting point + Vector2f origin = projectVertex(segments[0].getStart(), getWidth(), getHeight()); + calculatedPath.moveTo(origin.x, origin.y); + + // Draw the path segments + for (PathSegment segment : segments) { + Vector2f startProjected = projectVertex(segment.getStart(), getWidth()/2, getHeight()); + Vector2f endProjected = projectVertex(segment.getEnd(), getWidth()/2, getHeight()); + calculatedPath.lineTo(startProjected.x, startProjected.y); + calculatedPath.lineTo(endProjected.x, endProjected.y); + } + + return calculatedPath; + } + + @Override public void onDraw(Canvas canvas) { // Draw the sport preview canvas + + + } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java new file mode 100644 index 0000000..5507c71 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java @@ -0,0 +1,124 @@ +package com.example.fitbot.util.path; + +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GesturePath { + + // The vectors that make up the path. + private final PathSegment[] segments; + private double curvature; + + public GesturePath(Vector3f[] vectors) { + this(vectors, 0.0D); + } + + /** + * Create a new gesture path with a given set of vectors and curvature. + * + * @param vectors The vectors that make up the path. + * @param curvature The curvature of the path. + */ + public GesturePath(Vector3f[] vectors, double curvature) + { + if ( vectors.length < 2) + throw new IllegalArgumentException("A path must have at least two points."); + + this.curvature = curvature; + this.segments = new PathSegment[vectors.length - 1]; + for ( int i = 0; i < vectors.length - 1; i++) + segments[i] = new PathSegment(vectors[i], vectors[i + 1]); + } + + /** + * Constructor for a GesturePath with provided PathSegments. + * @param segments The PathSegments to initialize the path with. + */ + public GesturePath(PathSegment... segments) { + this.segments = segments; + this.curvature = 0.0d; + } + + /** + * Getter method for retrieving the path segments of this GesturePath. + * + * @return The path segments. + */ + public PathSegment[] getSegments() { + return segments; + } + + /** + * Method for retrieving the closest path segment to a reference point. + * + * @param reference The reference point to find the closest path segment to. + * @return The closest path segment to the reference point. + */ + public PathSegment closest(Vector3f reference) { + // If there's only one segment, return that one. + if ( segments.length == 1) + return segments[0]; + + return Arrays + .stream(segments) + .reduce(segments[0], (a, b) -> PathSegment.closer(a, b, reference)); + } + + /** + * 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(Vector3f referencePoint) { + return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error. + } + + // 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(Vector3f vector) { + vectors.add(vector); + return this; + } + + /** + * Builds the GesturePath object. + * + * @return The GesturePath object. + */ + public GesturePath build() { + return new GesturePath(vectors.toArray(new Vector3f[0])); + } + + } + +} diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java new file mode 100644 index 0000000..05f58a7 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java @@ -0,0 +1,175 @@ +package com.example.fitbot.util.path; + +import org.joml.Vector3f; + +public class PathSegment { + + private final double curvature; + private final Vector3f start, end, normal; + private final double horizontalDistance; + private final double distance; + private final double vectorAngle; // The angle of the vector from the start to the end point. + + /** + * Constructor for creating a PathSegment of two lines, with the normal vector + * pointing straight upwards relative to the line, with a curvature of 0.0. + * + * @param start The starting point of the line segment + * @param end The end point of the line segment. + */ + public PathSegment(Vector3f start, Vector3f end) { + this.curvature = 0.0D; + this.start = start; + this.end = end; + this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); + this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); + this.distance = start.distance(end); + + // Normal vector calculation + double horizontalAngle = Math.atan2(end.z - start.z, end.x - start.x); + double verticalAngle = Math.atan2(end.y - start.y, horizontalDistance); + float sinVertical = (float)Math.sin(verticalAngle); + float cosVertical = (float)Math.cos(verticalAngle); + float sinHorizontal = (float)Math.sin(horizontalAngle); + float cosHorizontal = (float)Math.cos(horizontalAngle); + // The normal vector faces directly upward relative to the line between start and end. + // This means the normal vector is the perpendicular bisecting line from the line segment. + this.normal = new Vector3f( + sinVertical * cosHorizontal, + cosVertical, + sinVertical * sinHorizontal + ); + } + + /** + * Create a new path segment with a given start and end point. + * + * @param start The start point of the path segment. + * @param end The end point of the path segment. + * A value of 0 will result in the normal vector pointing upwards. + * @param normal The normal vector of the path segment. + * This vector determines how the line curves, if it has a positive curvature. + * @param curvature The curvature of the path segment. + * This value is between -1 and 1, and is used to determine the curvature of the path, + * around the normal vector. + */ + public PathSegment(Vector3f start, Vector3f end, Vector3f normal, double curvature) { + this.curvature = curvature; + this.start = start; + this.end = end; + this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); + this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); + this.normal = normal; + this.distance = start.distance(end); + } + + /** + * Method that interpolates between the start and end points of the path segment, + * depending on the curvature of the curve. If the curvature is unset, or set to 0 + * then this method will use linear interpolation. Otherwise, it will use a curve. + * + * @param dst The destination vector to interpolate to. + * @param t The interpolation value between 0 and 1. + */ + public void interpolate(Vector3f dst, double t) { + t = Math.min(1.0D, Math.max(0.0D, t)); // Ensure boundaries + + // If curvature is 0, use linear interpolation. + if (this.curvature == 0.0D) { + dst.set(start).lerp(end, (float) t); + } else { + // Interpolate over the ellipse. + double angle = t * Math.PI + vectorAngle; // Angle for on the ellipse + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + double + + // Calculate the ellipse. + + + } + } + + /** + * Method for returning the control point of the path segment. + * This point is the point that the path segment curves around. + * + * @return The control point of the path segment. + */ + public Vector3f getControlPoint() { + return new Vector3f( + (this.start.x + this.end.x) / 2.0F, + (this.start.y + this.end.y) / 2.0F, + (this.start.z + this.end.z) / 2.0F + ) + .add(this.normal + .mul((float) this.curvature * (float) this.distance / 2.0F)); + } + + /** + * Method for calculating the difference between the provided vector and the + * path segment, depending on the normal vector and the curvature. + * If the provided vector does not lie on the path segment, this method will return + * the linear distance to the path. + * + * @param other The vector to calculate the difference to. + * @return The difference between the vector and the path segment. + */ + public double difference(Vector3f other) { + return 0.0D; // TODO: Implement. + } + + /** + * Get the curvature of the path segment. + * + * @return The curvature of the path segment. + */ + public double getVectorAngle() { + return this.vectorAngle; + } + + /** + * Get the normal vector of the path segment. + * + * @return The normal vector of the path segment. + */ + public Vector3f getStart() { + return start; + } + + /** + * Get the end point of the path segment. + * + * @return The end point of the path segment. + */ + public Vector3f getEnd() { + return end; + } + + /** + * Method for returning the distance to the closest point on the path segment. + * + * @param reference The reference point to calculate the distance to. + * @return The distance to the closest point on the path segment. + */ + public double distance(Vector3f reference) { + if ( this.start.distanceSquared(reference) > this.end.distanceSquared(reference)) + return this.end.distance(reference); + return this.start.distance(reference); + } + + /** + * Function for returning the closest path segment to a reference point. + * + * @param first The first path segment to compare. + * @param second The second path segment to compare. + * @param referencePoint The reference point to compare to. + * @return The closest path segment to the reference point. + */ + public static PathSegment closer(PathSegment first, PathSegment second, Vector3f referencePoint) { + if (first.distance(referencePoint) < second.distance(referencePoint)) + return first; + return second; + } +} 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/path/Point3D.java similarity index 78% rename from code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java rename to code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java index 47c251c..de14c78 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/path/Point3D.java @@ -1,9 +1,9 @@ -package com.example.fitbot.util.processing; +package com.example.fitbot.util.path; import java.util.Arrays; import java.util.Comparator; -public class Vector3 { +public class Point3D { public double x, y, z; @@ -14,7 +14,7 @@ public class Vector3 { * @param y The Y component of the vector. * @param z The Z component of the vector. */ - public Vector3(double x, double y, double z) { + public Point3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; @@ -25,8 +25,8 @@ public class Vector3 { * * @return A new vector with the same values. */ - public Vector3 copy() { - return new Vector3(this.x, this.y, this.z); + public Point3D copy() { + return new Point3D(this.x, this.y, this.z); } /** @@ -34,8 +34,8 @@ public class Vector3 { * * @return The zero vector. */ - public static Vector3 zero() { - return new Vector3(0, 0, 0); + public static Point3D zero() { + return new Point3D(0, 0, 0); } /** @@ -52,10 +52,10 @@ public class Vector3 { * * @return The normalized vector. */ - public Vector3 normalize() { + public Point3D normalize() { double mag = this.magnitude(); if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector."); - return new Vector3(this.x / mag, this.y / mag, this.z / mag); + return new Point3D(this.x / mag, this.y / mag, this.z / mag); } /** @@ -64,8 +64,8 @@ public class Vector3 { * @param other The other vector to subtract. * @return The new vector. */ - public Vector3 subtract(Vector3 other) { - return new Vector3(this.x - other.x, this.y - other.y, this.z - other.z); + public Point3D subtract(Point3D other) { + return new Point3D(this.x - other.x, this.y - other.y, this.z - other.z); } /** @@ -74,8 +74,8 @@ public class Vector3 { * @param other The other vector to add. * @return The new vector. */ - public Vector3 add(Vector3 other) { - return new Vector3(this.x + other.x, this.y + other.y, this.z + other.z); + public Point3D add(Point3D other) { + return new Point3D(this.x + other.x, this.y + other.y, this.z + other.z); } /** @@ -84,8 +84,8 @@ public class Vector3 { * @param scalar The scalar to multiply by. * @return The multiplied vector. */ - public Vector3 multiply(double scalar) { - return new Vector3(this.x * scalar, this.y * scalar, this.z * scalar); + public Point3D multiply(double scalar) { + return new Point3D(this.x * scalar, this.y * scalar, this.z * scalar); } /** @@ -94,9 +94,9 @@ public class Vector3 { * @param scalar The scalar to divide by. * @return The divided vector. */ - public Vector3 divide(double scalar) { + public Point3D divide(double scalar) { if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero."); - return new Vector3(this.x / scalar, this.y / scalar, this.z / scalar); + return new Point3D(this.x / scalar, this.y / scalar, this.z / scalar); } /** @@ -104,8 +104,8 @@ public class Vector3 { * * @return The negated vector. */ - public Vector3 negate() { - return new Vector3(-this.x, -this.y, -this.z); + public Point3D negate() { + return new Point3D(-this.x, -this.y, -this.z); } /** @@ -116,7 +116,7 @@ public class Vector3 { * @param radZ Rotation around the Z axis in radians. * @return The rotated vector. */ - public Vector3 rotate(double radX, double radY, double radZ) { + public Point3D rotate(double radX, double radY, double radZ) { double cosX = Math.cos(radX); double cosY = Math.cos(radY); double cosZ = Math.cos(radZ); @@ -126,7 +126,7 @@ public class Vector3 { double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY; double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY; double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY; - return new Vector3(newX, newY, newZ); + return new Point3D(newX, newY, newZ); } /** @@ -135,7 +135,7 @@ public class Vector3 { * @param rotation The rotation vector. * @return The rotated vector. */ - public Vector3 rotate(Vector3 rotation) { + public Point3D rotate(Point3D rotation) { return rotate(rotation.x, rotation.y, rotation.z); } @@ -145,10 +145,10 @@ public class Vector3 { * @param angle Rotation around the X axis in radians. * @return The rotated vector. */ - public Vector3 rotateX(double angle) { + public Point3D rotateX(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x, y * cosTheta - z * sinTheta, y * sinTheta + z * cosTheta @@ -161,10 +161,10 @@ public class Vector3 { * @param angle Rotation around the Y axis in radians. * @return The rotated vector. */ - public Vector3 rotateY(double angle) { + public Point3D rotateY(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x * cosTheta + z * sinTheta, y, -x * sinTheta + z * cosTheta @@ -177,10 +177,10 @@ public class Vector3 { * @param angle Rotation around the Z axis in radians. * @return The rotated vector. */ - public Vector3 rotateZ(double angle) { + public Point3D rotateZ(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x * cosTheta - y * sinTheta, x * sinTheta + y * cosTheta, z @@ -193,7 +193,7 @@ public class Vector3 { * @param compare The other vector. * @return The squared distance between the two vectors. */ - public double distanceSq(Vector3 compare) { + public double distanceSq(Point3D compare) { return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2); } @@ -203,7 +203,7 @@ public class Vector3 { * @param compare The other vector. * @return The distance between the two vectors. */ - public double distance(Vector3 compare) { + public double distance(Point3D compare) { return Math.sqrt(distanceSq(compare)); } @@ -214,7 +214,7 @@ public class Vector3 { * @param lineEnd The ending point of the line. * @return The distance to the line. */ - public double distanceToLine(Vector3 lineStart, Vector3 lineEnd) { + public double distanceToLine(Point3D lineStart, Point3D lineEnd) { double lineDistance = lineStart.distanceSq(lineEnd); if (lineDistance == 0) return this.distanceSq(lineStart); @@ -225,7 +225,7 @@ public class Vector3 { t = Math.max(0, Math.min(1, t)); - return this.distanceSq(new Vector3( + return this.distanceSq(new Point3D( lineStart.x + t * (lineEnd.x - lineStart.x), lineStart.y + t * (lineEnd.y - lineStart.y), lineStart.z + t * (lineEnd.z - lineStart.z)) @@ -238,16 +238,16 @@ public class Vector3 { * @param vectors The list of vectors to compare. * @return The closest vector. */ - public Vector3 closest(Vector3 ... vectors) { + public Point3D closest(Point3D... vectors) { return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null); } - public Vector3 map(VectorMapFunction function) { + public Point3D map(VectorMapFunction function) { return function.apply(this); } public interface VectorMapFunction { - Vector3 apply(Vector3 vector); + Point3D apply(Point3D vector); } @Override 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 deleted file mode 100644 index 451de9d..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.example.fitbot.util.processing; - -import java.util.ArrayList; -import java.util.List; - -public class GesturePath { - - // The vectors that make up the path. - private final Vector3[] vectors; - - public GesturePath(Vector3[] vectors) { - this.vectors = vectors; - } - - /** - * 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) { - // If there are no vectors, return 0. - if ( vectors.length == 0) - return 0; - - // If there's only one vector, return the distance to that vector. - if ( vectors.length == 1) - return vectors[0].distance(referencePoint); - - double distance = Double.MAX_VALUE; - double currentDistSq, nextDistSq; - int closestVectorIdx = 0; - - // Acquire two closest points to the reference point. - for ( int i = 0; i < vectors.length - 1; i++) { - currentDistSq = vectors[i].distanceSq(referencePoint); - nextDistSq = vectors[i + 1].distanceSq(referencePoint); - - if ( currentDistSq < distance) { - distance = currentDistSq; - closestVectorIdx = i; - } else if ( nextDistSq < distance) { - distance = nextDistSq; - closestVectorIdx = i + 1; - i++; // Skip the next iteration; this point is already closer. - } - } - - // Calculate the error between the two closest points. - Vector3 pointB = (closestVectorIdx == vectors.length - 1) ? - vectors[closestVectorIdx - 1] : // If the closest point is the last point, use the 1 to last one - (closestVectorIdx > 0 && // Find the closer point between the surrounding points. - (vectors[closestVectorIdx - 1].distanceSq(referencePoint) < vectors[closestVectorIdx + 1].distanceSq(referencePoint))) ? - vectors[closestVectorIdx - 1] : - vectors[closestVectorIdx + 1]; - - return referencePoint.distanceToLine(vectors[closestVectorIdx], pointB); - } - - // 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 2c10030..37e016f 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 @@ -1,11 +1,13 @@ package com.example.fitbot.util.processing; +import org.joml.Vector3f; + import java.util.Objects; public class MotionData { // Data of the motion sensor - public Vector3 acceleration, rotation; + public Vector3f acceleration, rotation; // Delimiter for the data received from the motion sensor private static final String DATA_DELIMITER = ";"; @@ -20,9 +22,9 @@ public class MotionData { * @param rotationY The rotation in the Y axis in degrees. * @param rotationZ The rotation in the Z axis in degrees. */ - public MotionData(double accelerationX, double accelerationY, double accelerationZ, double rotationX, double rotationY, double rotationZ) { - this.acceleration = new Vector3(accelerationX, accelerationY, accelerationZ); - this.rotation = new Vector3(rotationX, rotationY, rotationZ); + public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ) { + this.acceleration = new Vector3f(accelerationX, accelerationY, accelerationZ); + this.rotation = new Vector3f(rotationX, rotationY, rotationZ); } /** @@ -31,7 +33,7 @@ public class MotionData { * @param acceleration The acceleration vector in m/s^2. * @param rotation The rotation vector in degrees. */ - public MotionData(Vector3 acceleration, Vector3 rotation) { + public MotionData(Vector3f acceleration, Vector3f rotation) { this.acceleration = acceleration; this.rotation = rotation; } @@ -52,12 +54,12 @@ public class MotionData { return null; return new MotionData( - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2]), - Double.parseDouble(parts[3]), - Double.parseDouble(parts[4]), - Double.parseDouble(parts[5]) + Float.parseFloat(parts[0]), + Float.parseFloat(parts[1]), + Float.parseFloat(parts[2]), + Float.parseFloat(parts[3]), + Float.parseFloat(parts[4]), + Float.parseFloat(parts[5]) ); } } 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 43539ba..b4e7de3 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 @@ -2,10 +2,12 @@ package com.example.fitbot.util.processing; import android.util.Log; +import com.example.fitbot.util.path.GesturePath; import com.example.fitbot.util.server.IWebSocketHandler; import com.example.fitbot.util.server.WebSocket; import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; import java.util.ArrayList; import java.util.List; @@ -16,9 +18,10 @@ public class MotionProcessor { public static final String DELIMITER = ";"; private final List preprocessedData = new ArrayList<>(); // Preprocessed motion data - 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 + private final List relativePath = new ArrayList<>(); // Relative path of the motion data + private Vector3f ZERO = new Vector3f(0, 0, 0); + + private float sampleRate = 1.0F; // samples/second private DataConsumer motionDataConsumer = (p1, p2, p3, p4) -> {}; private GesturePath path; private WebSocket socket; @@ -77,14 +80,14 @@ public class MotionProcessor { // Otherwise check if it starts with 'calibrate', this is the ZERO point. } else if ( data.startsWith("zero")) { // message to calibrate device String[] vectorData = data.split(" ")[1].split(DELIMITER); - ZERO = new Vector3( + ZERO = new Vector3f( Float.parseFloat(vectorData[0]), Float.parseFloat(vectorData[1]), Float.parseFloat(vectorData[2]) ); Log.i("MotionProcessor", "Device calibrated at " + ZERO.toString()); } else if ( data.startsWith("sampleRate")) { - this.sampleRate = Double.parseDouble(data.split(" ")[1]); + this.sampleRate = Float.parseFloat(data.split(" ")[1]); } } @@ -104,8 +107,8 @@ public class MotionProcessor { */ public void addMotionData(MotionData data) { preprocessedData.add(data); - Vector3 previous = this.relativePath.isEmpty() ? ZERO : this.relativePath.get(this.relativePath.size() - 1); - Vector3 relativeVector = getRelativeVector(data).add(previous); + Vector3f previous = this.relativePath.isEmpty() ? ZERO : this.relativePath.get(this.relativePath.size() - 1); + Vector3f relativeVector = getRelativeVector(data).add(previous); this.relativePath.add(relativeVector); motionDataConsumer.accept(relativeVector, data, this.relativePath.size(), this.sampleRate); } @@ -115,7 +118,7 @@ public class MotionProcessor { * * @param relativePath The new relative path. */ - public void setRelativePath(List relativePath) { + public void setRelativePath(List relativePath) { this.relativePath.clear(); this.relativePath.addAll(relativePath); } @@ -138,15 +141,17 @@ public class MotionProcessor { * @param motionData The motion data to calculate the relative vector for. * @return The relative vector of the motion data. */ - public Vector3 getRelativeVector(MotionData motionData) { + public Vector3f 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()) - .divide(2) - .multiply(sampleRate * sampleRate); + .rotateX(-motionData.rotation.x) + .rotateY(-motionData.rotation.y) + .rotateZ(-motionData.rotation.z) + .div(2) + .mul(sampleRate * sampleRate); } /** @@ -185,7 +190,7 @@ public class MotionProcessor { * @param referencePoint The reference point to compare the motion data to. * @return The error of the motion data compared to the reference path. */ - public double getError(GesturePath path, Vector3 referencePoint) + public double getError(GesturePath path, Vector3f referencePoint) { return path.getError(referencePoint); } @@ -197,7 +202,7 @@ public class MotionProcessor { * @param referencePoint The reference point to compare the path data to. * @return The error of the motion data compared to the reference path. */ - public double getError(Vector3 referencePoint) { + public double getError(Vector3f referencePoint) { if ( path == null) return 0; return path.getError(referencePoint); @@ -254,6 +259,6 @@ public class MotionProcessor { * @param sampleIndex The index of the current sample * @param sampleRate The sample rate. */ - void accept(Vector3 transformedVector, MotionData motionData, int sampleIndex, double sampleRate); + void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate); } } From 86a96740e50a38f95568915e8435f42ee408691a Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 15:47:52 +0200 Subject: [PATCH 02/11] Updated onderzoek-voorbeeld.md & motion-tracking-system-analysis.md --- .../math-expression-acceleration-vector.png | Bin 0 -> 3607 bytes .../assets/motion-path-example-path-error.png | Bin 0 -> 3762 bytes .../assets/motion-path-example-vertices.png | Bin 0 -> 863 bytes .../motion-tracking-system-analysis.md | 6 ++++-- .../literatuuronderzoek/onderzoek-voorbeeld.md | 7 +++++++ 5 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 docs/documentation/assets/math-expression-acceleration-vector.png create mode 100644 docs/documentation/assets/motion-path-example-path-error.png create mode 100644 docs/documentation/assets/motion-path-example-vertices.png diff --git a/docs/documentation/assets/math-expression-acceleration-vector.png b/docs/documentation/assets/math-expression-acceleration-vector.png new file mode 100644 index 0000000000000000000000000000000000000000..a818694b8f49d8d94bd2c13ed24f71e6dbcda09d GIT binary patch literal 3607 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 literal 0 HcmV?d00001 diff --git a/docs/documentation/assets/motion-path-example-path-error.png b/docs/documentation/assets/motion-path-example-path-error.png new file mode 100644 index 0000000000000000000000000000000000000000..a92c0aae3fbd60baad8023550be6126a8106b75f GIT binary patch literal 3762 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 LVWeHHX&?1J?vU~V literal 0 HcmV?d00001 diff --git a/docs/documentation/assets/motion-path-example-vertices.png b/docs/documentation/assets/motion-path-example-vertices.png new file mode 100644 index 0000000000000000000000000000000000000000..8505cfb6c6c64e12e4a7ddfdbcb8d88d3dd5799d GIT binary patch literal 863 zcmeAS@N?(olHy`uVBq!ia0vp^4Is?H1|$#LC7uRSEX7WqAsj$Z!;#X#z`!i#>Eakt zG3V`_v)&#l3AQj|!SVBnS)shUzQE-ooo zluUdyl(vc+uVFk`uOQnYrpUr}@W6{>Q7r46-X*=eGxK+i{ci5!9s7??FFu!+r?fMy zo+UaokiQ{Au)&haz@6b4#{n6}8OjXBED3!KXUGs#U^AL+TxIhisHiU+j6xFQDqGh85uV=z51NRp}?jWyKlwPrK+Bn z4y|XsyQkFl!z~v1j*j*ctF=DM95-){57iK1S`T!a3?s$PB-)qPLR3qYUnzd5KYR9e z>5{iIghDybowR&Pq8o{-H z_nvzGdD4;^yY>72-3z#P`1+yK5x%~?QqtSCrq7PHyaPjf6#whr+vTHv z{EQYqSMF--7x?k~0tPkpWm~eJ=XnM#47l)%_o|?Oh|L=YMjl)FE1y1XQpqfM5qadY zh`6SkNXiBIzTW< +Path Point Example To be able to measure the position of our tracking device, we'll have to use sensors that allow us to retrieve useful information that can provide us with either position or velocity. The device will have @@ -45,4 +45,6 @@ Second, to convert our relative acceleration rotation to a useful position, we'l acceleration vector `A(x, y, z)` and rotation vector `R(x, y, z)` to an acceleration vector that is perpendicular to the normal vector of the earth. This is because the acceleration vector of the device is relative to its own axes, and not to the earth's normal vector. -To convert this, we'll have to multiply the acceleration vector `A(x, y, z)` by the rotation matrix \ No newline at end of file +To convert this, we'll have to multiply the acceleration vector `A(x, y, z)` by the rotation matrix +with negative angles, to rotate it back to be perpendicular with the normal of the earth. +After \ No newline at end of file diff --git a/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md b/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md index 90bf854..fb20ae4 100644 --- a/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md +++ b/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md @@ -44,6 +44,13 @@ om deze toe te passen om te helpen met het eenzaamheidsprobleem bij ouderen. ### Op welke manier kunnen we kunstmatige intelligentie inzetten om eenzaamheid onder ouderen te bestrijden? +Zoals al eerder was vermeld worden robots al langer gebruikt bij het helpen van ouderen met huishoudelijke taken die voor hen te zwaar zijn. +Daarom valt het idee ook te overwegen om robots in te zetten om ouderen te helpen met het bestrijden van eenzaamheid. Hiervoor kan er +gebruik gemaakt worden van kunstmatige intelligentie. Kunstmatige intelligentie kan gebruikt worden om de robot te leren hoe het om moet gaan met +de gebruiker, om zowel te horen wat de gebruiker zegt, inhoudelijke reacties te geven op de gebruiker of om de gebruiker te helpen met bepaalde taken als ernaar +gevraagd wordt. Dit kan ervoor zorgen dat de ouderen zich minder eenzaam voelen, en dat ze zich meer op hun gemak voelen in hun eigen huis. + + ### Hoe kunnen we ervoor zorgen dat dit gedaan kan worden zonder de privacy van ouderen te schenden? From 8224327a0182306949ddb3872d42f4480e19caf1 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:29:22 +0200 Subject: [PATCH 03/11] bread --- code/src/Fitbot/.idea/misc.xml | 1 + .../PersonalMotionPreviewElement.java | 40 ++- .../example/fitbot/util/path/PathSegment.java | 105 ++----- .../com/example/fitbot/util/path/Point3D.java | 258 ------------------ .../src/main/res/layout/activity_fitness.xml | 10 + 5 files changed, 56 insertions(+), 358 deletions(-) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java create mode 100644 code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml diff --git a/code/src/Fitbot/.idea/misc.xml b/code/src/Fitbot/.idea/misc.xml index 135ab11..a67315e 100644 --- a/code/src/Fitbot/.idea/misc.xml +++ b/code/src/Fitbot/.idea/misc.xml @@ -22,6 +22,7 @@ + diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java index d70c88c..32631ee 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java @@ -2,12 +2,12 @@ package com.example.fitbot.ui.components; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Path; import android.view.View; import com.example.fitbot.util.path.GesturePath; import com.example.fitbot.util.path.PathSegment; -import com.example.fitbot.util.path.Point3D; import com.example.fitbot.util.processing.MotionData; import com.example.fitbot.util.processing.MotionProcessor; @@ -22,6 +22,7 @@ public class PersonalMotionPreviewElement extends View { private double pathTime = 0.0D; // The timestamp at which the path is currently at. private MotionProcessor motionProcessor; private Path referencePath, performingPath; + private Paint referencePaint, performingPaint; /** * Constants for the preview path projection. @@ -45,22 +46,41 @@ public class PersonalMotionPreviewElement extends View { this.motionProcessor = new MotionProcessor(); this.motionProcessor.startListening(); this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate) -> { - // TODO: Implement the calculation of the drawing path + // TODO: Implement the calculation of the `performingPath` based on the motion data }); - this.referencePath = generatePath() + this.referencePath = getDrawablePath(path.getSegments()); this.performingPath = new Path(); + + this.referencePaint = new Paint(); + this.referencePaint.setColor(-1); // White + this.referencePaint.setStyle(Paint.Style.STROKE); + this.referencePaint.setStrokeWidth(5.0f); + this.performingPaint = new Paint(); + this.performingPaint.setColor(0xFF0000FF); // Blue + this.performingPaint.setStyle(Paint.Style.STROKE); + this.performingPaint.setStrokeWidth(5.0f); } /** * Method that calculates the path that will be drawn on the * canvas. This method will be called every time new motion data is received. */ - private void calculateDrawingPath(Point3D transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { + private void calculateDrawingPath(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { // Recalculate the personal path based on the new motion data } + /** + * Method for setting the gesture path that will be drawn on the canvas. + * + * @param path The gesture path to draw. + */ + public void setGesturePath(GesturePath path) { + this.path = path; + this.referencePath = getDrawablePath(path.getSegments()); + } + /** * Method for setting the rotation of the preview path. * @@ -111,17 +131,12 @@ public class PersonalMotionPreviewElement extends View { * Method that converts a sequence of vectors to a Path object. * This path is a set of bezier curves that will be drawn on the canvas. * - * @param curvature The curvature of the bezier curves. - * This number must be between 0 and 1, and it represents - * by how much the path segments will be curved. - * A value of 0 represents no curvature at all, - * while values closer to 1 approach full circular curvature. * @param segments The path segments in the path. * These segments will be connected by bezier curves, which * all have unique curvature values. * @return The generated path object. */ - private Path getDrawablePath(double curvature, PathSegment... segments) { + private Path getDrawablePath(PathSegment... segments) { Path calculatedPath = new Path(); @@ -144,8 +159,7 @@ public class PersonalMotionPreviewElement extends View { @Override public void onDraw(Canvas canvas) { // Draw the sport preview canvas - - - + canvas.drawPath(referencePath, referencePaint); + canvas.drawPath(performingPath, performingPaint); } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java index 05f58a7..9f0c361 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java @@ -4,11 +4,8 @@ import org.joml.Vector3f; public class PathSegment { - private final double curvature; - private final Vector3f start, end, normal; - private final double horizontalDistance; + private final Vector3f start, end; private final double distance; - private final double vectorAngle; // The angle of the vector from the start to the end point. /** * Constructor for creating a PathSegment of two lines, with the normal vector @@ -18,48 +15,8 @@ public class PathSegment { * @param end The end point of the line segment. */ public PathSegment(Vector3f start, Vector3f end) { - this.curvature = 0.0D; this.start = start; this.end = end; - this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); - this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); - this.distance = start.distance(end); - - // Normal vector calculation - double horizontalAngle = Math.atan2(end.z - start.z, end.x - start.x); - double verticalAngle = Math.atan2(end.y - start.y, horizontalDistance); - float sinVertical = (float)Math.sin(verticalAngle); - float cosVertical = (float)Math.cos(verticalAngle); - float sinHorizontal = (float)Math.sin(horizontalAngle); - float cosHorizontal = (float)Math.cos(horizontalAngle); - // The normal vector faces directly upward relative to the line between start and end. - // This means the normal vector is the perpendicular bisecting line from the line segment. - this.normal = new Vector3f( - sinVertical * cosHorizontal, - cosVertical, - sinVertical * sinHorizontal - ); - } - - /** - * Create a new path segment with a given start and end point. - * - * @param start The start point of the path segment. - * @param end The end point of the path segment. - * A value of 0 will result in the normal vector pointing upwards. - * @param normal The normal vector of the path segment. - * This vector determines how the line curves, if it has a positive curvature. - * @param curvature The curvature of the path segment. - * This value is between -1 and 1, and is used to determine the curvature of the path, - * around the normal vector. - */ - public PathSegment(Vector3f start, Vector3f end, Vector3f normal, double curvature) { - this.curvature = curvature; - this.start = start; - this.end = end; - this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); - this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); - this.normal = normal; this.distance = start.distance(end); } @@ -71,40 +28,9 @@ public class PathSegment { * @param dst The destination vector to interpolate to. * @param t The interpolation value between 0 and 1. */ - public void interpolate(Vector3f dst, double t) { - t = Math.min(1.0D, Math.max(0.0D, t)); // Ensure boundaries - - // If curvature is 0, use linear interpolation. - if (this.curvature == 0.0D) { - dst.set(start).lerp(end, (float) t); - } else { - // Interpolate over the ellipse. - double angle = t * Math.PI + vectorAngle; // Angle for on the ellipse - double cos = Math.cos(angle); - double sin = Math.sin(angle); - - double - - // Calculate the ellipse. - - - } - } - - /** - * Method for returning the control point of the path segment. - * This point is the point that the path segment curves around. - * - * @return The control point of the path segment. - */ - public Vector3f getControlPoint() { - return new Vector3f( - (this.start.x + this.end.x) / 2.0F, - (this.start.y + this.end.y) / 2.0F, - (this.start.z + this.end.z) / 2.0F - ) - .add(this.normal - .mul((float) this.curvature * (float) this.distance / 2.0F)); + public Vector3f interpolate(Vector3f dst, double t) { + return new Vector3f(this.start) + .lerp(this.end, (float) Math.min(1.0F, Math.max(0.0F, t))); } /** @@ -117,16 +43,21 @@ public class PathSegment { * @return The difference between the vector and the path segment. */ public double difference(Vector3f other) { - return 0.0D; // TODO: Implement. - } - /** - * Get the curvature of the path segment. - * - * @return The curvature of the path segment. - */ - public double getVectorAngle() { - return this.vectorAngle; + if (this.distance == 0) + return this.start.distance(other); + + double t = ((other.x - this.start.x) * (this.end.x - this.start.x) + + (other.y - this.start.y) * (this.end.y - this.start.y) + + (other.z - this.start.z) * (this.end.z - this.start.z)) / distance; + + t = Math.max(0, Math.min(1, t)); + + return other.distance(new Vector3f( + (float) (this.start.x + t * (this.end.x - this.start.x)), + (float) (this.start.y + t * (this.end.y - this.start.y)), + (float) (this.start.z + t * (this.end.z - this.start.z)) + )); } /** diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java deleted file mode 100644 index de14c78..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.example.fitbot.util.path; - -import java.util.Arrays; -import java.util.Comparator; - -public class Point3D { - - public double x, y, z; - - /** - * Constructor for creating a new vector. - * - * @param x The X component of the vector. - * @param y The Y component of the vector. - * @param z The Z component of the vector. - */ - public Point3D(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Copy the vector. - * - * @return A new vector with the same values. - */ - public Point3D copy() { - return new Point3D(this.x, this.y, this.z); - } - - /** - * Get the zero vector. - * - * @return The zero vector. - */ - public static Point3D zero() { - return new Point3D(0, 0, 0); - } - - /** - * Get the magnitude of the vector. - * - * @return The magnitude of the vector. - */ - public double magnitude() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - /** - * Normalize the vector. - * - * @return The normalized vector. - */ - public Point3D normalize() { - double mag = this.magnitude(); - if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector."); - return new Point3D(this.x / mag, this.y / mag, this.z / mag); - } - - /** - * Subtract the vector from another vector. - * - * @param other The other vector to subtract. - * @return The new vector. - */ - public Point3D subtract(Point3D other) { - return new Point3D(this.x - other.x, this.y - other.y, this.z - other.z); - } - - /** - * Add the vector to another vector. - * - * @param other The other vector to add. - * @return The new vector. - */ - public Point3D add(Point3D other) { - return new Point3D(this.x + other.x, this.y + other.y, this.z + other.z); - } - - /** - * Multiply the vector by a scalar. - * - * @param scalar The scalar to multiply by. - * @return The multiplied vector. - */ - public Point3D multiply(double scalar) { - return new Point3D(this.x * scalar, this.y * scalar, this.z * scalar); - } - - /** - * Divide the vector by a scalar. - * - * @param scalar The scalar to divide by. - * @return The divided vector. - */ - public Point3D divide(double scalar) { - if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero."); - return new Point3D(this.x / scalar, this.y / scalar, this.z / scalar); - } - - /** - * Negate the vector. - * - * @return The negated vector. - */ - public Point3D negate() { - return new Point3D(-this.x, -this.y, -this.z); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param radX Rotation around the X axis in radians. - * @param radY Rotation around the Y axis in radians. - * @param radZ Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotate(double radX, double radY, double radZ) { - double cosX = Math.cos(radX); - double cosY = Math.cos(radY); - double cosZ = Math.cos(radZ); - double sinX = Math.sin(radX); - double sinY = Math.sin(radY); - double sinZ = Math.sin(radZ); - double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY; - double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY; - double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY; - return new Point3D(newX, newY, newZ); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param rotation The rotation vector. - * @return The rotated vector. - */ - public Point3D rotate(Point3D rotation) { - return rotate(rotation.x, rotation.y, rotation.z); - } - - /** - * Rotate the vector around the X axis. - * - * @param angle Rotation around the X axis in radians. - * @return The rotated vector. - */ - public Point3D rotateX(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x, - y * cosTheta - z * sinTheta, - y * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Y axis. - * - * @param angle Rotation around the Y axis in radians. - * @return The rotated vector. - */ - public Point3D rotateY(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta + z * sinTheta, - y, - -x * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Z axis. - * - * @param angle Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotateZ(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta - y * sinTheta, - x * sinTheta + y * cosTheta, - z - ); - } - - /** - * 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(Point3D 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. - * - * @param compare The other vector. - * @return The distance between the two vectors. - */ - public double distance(Point3D compare) { - return Math.sqrt(distanceSq(compare)); - } - - /** - * Calculate the distance to a line defined by two points. - * - * @param lineStart The starting point of the line. - * @param lineEnd The ending point of the line. - * @return The distance to the line. - */ - public double distanceToLine(Point3D lineStart, Point3D lineEnd) { - double lineDistance = lineStart.distanceSq(lineEnd); - if (lineDistance == 0) - return this.distanceSq(lineStart); - - double t = ((this.x - lineStart.x) * (lineEnd.x - lineStart.x) + - (this.y - lineStart.y) * (lineEnd.y - lineStart.y) + - (this.z - lineStart.z) * (lineEnd.z - lineStart.z)) / lineDistance; - - t = Math.max(0, Math.min(1, t)); - - return this.distanceSq(new Point3D( - lineStart.x + t * (lineEnd.x - lineStart.x), - lineStart.y + t * (lineEnd.y - lineStart.y), - lineStart.z + t * (lineEnd.z - lineStart.z)) - ); - } - - /** - * Retrieve the closest vector to this one given a list of vectors. - * - * @param vectors The list of vectors to compare. - * @return The closest vector. - */ - public Point3D closest(Point3D... vectors) { - return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null); - } - - public Point3D map(VectorMapFunction function) { - return function.apply(this); - } - - public interface VectorMapFunction { - Point3D apply(Point3D vector); - } - - @Override - public String toString() - { - return "Vector3(" + this.x + ", " + this.y + ", " + this.z + ")"; - } -} diff --git a/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml new file mode 100644 index 0000000..35990c8 --- /dev/null +++ b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file From 606a05aa13d61c0346c54755717f164b08d5f836 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 01:10:51 +0200 Subject: [PATCH 04/11] Updated path math, started with 3d path to 2d screen space projection --- code/src/Fitbot/app/build.gradle | 1 + .../PersonalMotionPreviewElement.java | 132 +++++++++++-- .../example/fitbot/util/path/GesturePath.java | 124 +++++++++++++ .../example/fitbot/util/path/PathSegment.java | 175 ++++++++++++++++++ .../Vector3.java => path/Point3D.java} | 70 +++---- .../fitbot/util/processing/GesturePath.java | 104 ----------- .../fitbot/util/processing/MotionData.java | 24 +-- .../util/processing/MotionProcessor.java | 35 ++-- 8 files changed, 488 insertions(+), 177 deletions(-) create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java rename code/src/Fitbot/app/src/main/java/com/example/fitbot/util/{processing/Vector3.java => path/Point3D.java} (78%) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java diff --git a/code/src/Fitbot/app/build.gradle b/code/src/Fitbot/app/build.gradle index 38cf8be..f3a2fea 100644 --- a/code/src/Fitbot/app/build.gradle +++ b/code/src/Fitbot/app/build.gradle @@ -34,6 +34,7 @@ dependencies { implementation 'com.android.support.constraint:constraint-layout:2.0.4' implementation 'com.android.support:cardview-v7:28.0.0' implementation 'com.android.support:design:28.0.0' + implementation 'org.joml:joml:1.10.5' testImplementation 'junit:junit:4.13.2' androidTestImplementation 'com.android.support.test:runner:1.0.2' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java index b5161d8..d70c88c 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java @@ -5,39 +5,147 @@ import android.graphics.Canvas; import android.graphics.Path; import android.view.View; -import com.example.fitbot.util.processing.GesturePath; +import com.example.fitbot.util.path.GesturePath; +import com.example.fitbot.util.path.PathSegment; +import com.example.fitbot.util.path.Point3D; import com.example.fitbot.util.processing.MotionData; import com.example.fitbot.util.processing.MotionProcessor; -import com.example.fitbot.util.processing.Vector3; + +import org.joml.Matrix4f; +import org.joml.Vector2f; +import org.joml.Vector3f; +import org.joml.Vector4f; public class PersonalMotionPreviewElement extends View { private GesturePath path; private double pathTime = 0.0D; // The timestamp at which the path is currently at. private MotionProcessor motionProcessor; - private Path targetPath, personalPath; + private Path referencePath, performingPath; /** - * Method that calculates the path that will be drawn on the - * canvas. This method will be called every time new motion data is received. + * Constants for the preview path projection. */ - private void calculateDrawingPath(Vector3 transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { - - } - + private final float FOV = 70.0f; // The field of view of the preview path + private final float Z_NEAR = 0.1f; // The near clipping plane + private final float Z_FAR = 1000.0f; // The far clipping plane + private Vector3f cameraPosition = new Vector3f(0.0f, 0.0f, 0.0f); // The position of the camera + private Vector2f screenDimensions = new Vector2f(); // Width and height dimensions of the screen + private Vector2f rotation = new Vector2f(); // Rotation vector (yaw, pitch) + /** + * Constructor for the PersonalMotionPreviewElement class. + * + * @param context The context in which this element is created. + * @param path The gesture path that will be drawn on the canvas. + */ public PersonalMotionPreviewElement(Context context, GesturePath path) { super(context); this.path = path; this.motionProcessor = new MotionProcessor(); this.motionProcessor.startListening(); - this.motionProcessor.setMotionDataEventHandler(this::calculateDrawingPath); - this.targetPath = new Path(); - this.personalPath = new Path(); + this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate) -> { + // TODO: Implement the calculation of the drawing path + }); + + this.referencePath = generatePath() + this.performingPath = new Path(); } + /** + * Method that calculates the path that will be drawn on the + * canvas. This method will be called every time new motion data is received. + */ + private void calculateDrawingPath(Point3D transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { + // Recalculate the personal path based on the new motion data + + } + + /** + * Method for setting the rotation of the preview path. + * + * @param yaw The yaw rotation of the preview path. + * @param pitch The pitch rotation of the preview path. + */ + public void setRotation(float yaw, float pitch) { + this.rotation.set(Math.toRadians(yaw), Math.toRadians(pitch)); + } + + /** + * Method for projecting a 3D point onto the screen. + * This method converts the 3D point to 2D space using a Model-View-Projection matrix transformation. + * + * @param point The point to cast to the screen. + * @param virtualWidth The width of the virtual screen. + * This is used to normalize the screen coordinates. + * @param virtualHeight The height of the virtual screen. + * @return The transformed vector in screen coordinates ranging from (0, 0) to (virtualWidth, virtualHeight). + */ + private Vector2f projectVertex(Vector3f point, int virtualWidth, int virtualHeight) { + + Matrix4f modelViewMatrix = new Matrix4f() + .rotateX((float) Math.toRadians(rotation.x)) + .rotateY((float) Math.toRadians(rotation.y)) + .translate(cameraPosition); + + Matrix4f projectionMatrix = new Matrix4f() + .perspective((float) Math.toRadians(FOV), (float) virtualWidth / virtualHeight, Z_NEAR, Z_FAR); + + // Calculate Model-View-Projection matrix + Matrix4f MVP = new Matrix4f() + .set(projectionMatrix) + .mul(modelViewMatrix); + + // Convert to screen coordinates + Vector4f screenCoordinates = new Vector4f(point, 1.0f) + .mul(MVP); + + // Normalize screen coordinates from (-1, 1) to (0, virtualWidth) and (0, virtualHeight) + float normalizedX = (screenCoordinates.x / screenCoordinates.w + 1.0f) * 0.5f * virtualWidth; + float normalizedY = (1.0f - screenCoordinates.y / screenCoordinates.w) * 0.5f * virtualHeight; + + return new Vector2f(normalizedX, normalizedY); + } + + /** + * Method that converts a sequence of vectors to a Path object. + * This path is a set of bezier curves that will be drawn on the canvas. + * + * @param curvature The curvature of the bezier curves. + * This number must be between 0 and 1, and it represents + * by how much the path segments will be curved. + * A value of 0 represents no curvature at all, + * while values closer to 1 approach full circular curvature. + * @param segments The path segments in the path. + * These segments will be connected by bezier curves, which + * all have unique curvature values. + * @return The generated path object. + */ + private Path getDrawablePath(double curvature, PathSegment... segments) { + + Path calculatedPath = new Path(); + + // Starting point + Vector2f origin = projectVertex(segments[0].getStart(), getWidth(), getHeight()); + calculatedPath.moveTo(origin.x, origin.y); + + // Draw the path segments + for (PathSegment segment : segments) { + Vector2f startProjected = projectVertex(segment.getStart(), getWidth()/2, getHeight()); + Vector2f endProjected = projectVertex(segment.getEnd(), getWidth()/2, getHeight()); + calculatedPath.lineTo(startProjected.x, startProjected.y); + calculatedPath.lineTo(endProjected.x, endProjected.y); + } + + return calculatedPath; + } + + @Override public void onDraw(Canvas canvas) { // Draw the sport preview canvas + + + } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java new file mode 100644 index 0000000..5507c71 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java @@ -0,0 +1,124 @@ +package com.example.fitbot.util.path; + +import org.joml.Vector3f; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class GesturePath { + + // The vectors that make up the path. + private final PathSegment[] segments; + private double curvature; + + public GesturePath(Vector3f[] vectors) { + this(vectors, 0.0D); + } + + /** + * Create a new gesture path with a given set of vectors and curvature. + * + * @param vectors The vectors that make up the path. + * @param curvature The curvature of the path. + */ + public GesturePath(Vector3f[] vectors, double curvature) + { + if ( vectors.length < 2) + throw new IllegalArgumentException("A path must have at least two points."); + + this.curvature = curvature; + this.segments = new PathSegment[vectors.length - 1]; + for ( int i = 0; i < vectors.length - 1; i++) + segments[i] = new PathSegment(vectors[i], vectors[i + 1]); + } + + /** + * Constructor for a GesturePath with provided PathSegments. + * @param segments The PathSegments to initialize the path with. + */ + public GesturePath(PathSegment... segments) { + this.segments = segments; + this.curvature = 0.0d; + } + + /** + * Getter method for retrieving the path segments of this GesturePath. + * + * @return The path segments. + */ + public PathSegment[] getSegments() { + return segments; + } + + /** + * Method for retrieving the closest path segment to a reference point. + * + * @param reference The reference point to find the closest path segment to. + * @return The closest path segment to the reference point. + */ + public PathSegment closest(Vector3f reference) { + // If there's only one segment, return that one. + if ( segments.length == 1) + return segments[0]; + + return Arrays + .stream(segments) + .reduce(segments[0], (a, b) -> PathSegment.closer(a, b, reference)); + } + + /** + * 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(Vector3f referencePoint) { + return closest(referencePoint).difference(referencePoint); // Get the closest segment and calculate the error. + } + + // 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(Vector3f vector) { + vectors.add(vector); + return this; + } + + /** + * Builds the GesturePath object. + * + * @return The GesturePath object. + */ + public GesturePath build() { + return new GesturePath(vectors.toArray(new Vector3f[0])); + } + + } + +} diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java new file mode 100644 index 0000000..05f58a7 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java @@ -0,0 +1,175 @@ +package com.example.fitbot.util.path; + +import org.joml.Vector3f; + +public class PathSegment { + + private final double curvature; + private final Vector3f start, end, normal; + private final double horizontalDistance; + private final double distance; + private final double vectorAngle; // The angle of the vector from the start to the end point. + + /** + * Constructor for creating a PathSegment of two lines, with the normal vector + * pointing straight upwards relative to the line, with a curvature of 0.0. + * + * @param start The starting point of the line segment + * @param end The end point of the line segment. + */ + public PathSegment(Vector3f start, Vector3f end) { + this.curvature = 0.0D; + this.start = start; + this.end = end; + this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); + this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); + this.distance = start.distance(end); + + // Normal vector calculation + double horizontalAngle = Math.atan2(end.z - start.z, end.x - start.x); + double verticalAngle = Math.atan2(end.y - start.y, horizontalDistance); + float sinVertical = (float)Math.sin(verticalAngle); + float cosVertical = (float)Math.cos(verticalAngle); + float sinHorizontal = (float)Math.sin(horizontalAngle); + float cosHorizontal = (float)Math.cos(horizontalAngle); + // The normal vector faces directly upward relative to the line between start and end. + // This means the normal vector is the perpendicular bisecting line from the line segment. + this.normal = new Vector3f( + sinVertical * cosHorizontal, + cosVertical, + sinVertical * sinHorizontal + ); + } + + /** + * Create a new path segment with a given start and end point. + * + * @param start The start point of the path segment. + * @param end The end point of the path segment. + * A value of 0 will result in the normal vector pointing upwards. + * @param normal The normal vector of the path segment. + * This vector determines how the line curves, if it has a positive curvature. + * @param curvature The curvature of the path segment. + * This value is between -1 and 1, and is used to determine the curvature of the path, + * around the normal vector. + */ + public PathSegment(Vector3f start, Vector3f end, Vector3f normal, double curvature) { + this.curvature = curvature; + this.start = start; + this.end = end; + this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); + this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); + this.normal = normal; + this.distance = start.distance(end); + } + + /** + * Method that interpolates between the start and end points of the path segment, + * depending on the curvature of the curve. If the curvature is unset, or set to 0 + * then this method will use linear interpolation. Otherwise, it will use a curve. + * + * @param dst The destination vector to interpolate to. + * @param t The interpolation value between 0 and 1. + */ + public void interpolate(Vector3f dst, double t) { + t = Math.min(1.0D, Math.max(0.0D, t)); // Ensure boundaries + + // If curvature is 0, use linear interpolation. + if (this.curvature == 0.0D) { + dst.set(start).lerp(end, (float) t); + } else { + // Interpolate over the ellipse. + double angle = t * Math.PI + vectorAngle; // Angle for on the ellipse + double cos = Math.cos(angle); + double sin = Math.sin(angle); + + double + + // Calculate the ellipse. + + + } + } + + /** + * Method for returning the control point of the path segment. + * This point is the point that the path segment curves around. + * + * @return The control point of the path segment. + */ + public Vector3f getControlPoint() { + return new Vector3f( + (this.start.x + this.end.x) / 2.0F, + (this.start.y + this.end.y) / 2.0F, + (this.start.z + this.end.z) / 2.0F + ) + .add(this.normal + .mul((float) this.curvature * (float) this.distance / 2.0F)); + } + + /** + * Method for calculating the difference between the provided vector and the + * path segment, depending on the normal vector and the curvature. + * If the provided vector does not lie on the path segment, this method will return + * the linear distance to the path. + * + * @param other The vector to calculate the difference to. + * @return The difference between the vector and the path segment. + */ + public double difference(Vector3f other) { + return 0.0D; // TODO: Implement. + } + + /** + * Get the curvature of the path segment. + * + * @return The curvature of the path segment. + */ + public double getVectorAngle() { + return this.vectorAngle; + } + + /** + * Get the normal vector of the path segment. + * + * @return The normal vector of the path segment. + */ + public Vector3f getStart() { + return start; + } + + /** + * Get the end point of the path segment. + * + * @return The end point of the path segment. + */ + public Vector3f getEnd() { + return end; + } + + /** + * Method for returning the distance to the closest point on the path segment. + * + * @param reference The reference point to calculate the distance to. + * @return The distance to the closest point on the path segment. + */ + public double distance(Vector3f reference) { + if ( this.start.distanceSquared(reference) > this.end.distanceSquared(reference)) + return this.end.distance(reference); + return this.start.distance(reference); + } + + /** + * Function for returning the closest path segment to a reference point. + * + * @param first The first path segment to compare. + * @param second The second path segment to compare. + * @param referencePoint The reference point to compare to. + * @return The closest path segment to the reference point. + */ + public static PathSegment closer(PathSegment first, PathSegment second, Vector3f referencePoint) { + if (first.distance(referencePoint) < second.distance(referencePoint)) + return first; + return second; + } +} 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/path/Point3D.java similarity index 78% rename from code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/Vector3.java rename to code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java index 47c251c..de14c78 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/path/Point3D.java @@ -1,9 +1,9 @@ -package com.example.fitbot.util.processing; +package com.example.fitbot.util.path; import java.util.Arrays; import java.util.Comparator; -public class Vector3 { +public class Point3D { public double x, y, z; @@ -14,7 +14,7 @@ public class Vector3 { * @param y The Y component of the vector. * @param z The Z component of the vector. */ - public Vector3(double x, double y, double z) { + public Point3D(double x, double y, double z) { this.x = x; this.y = y; this.z = z; @@ -25,8 +25,8 @@ public class Vector3 { * * @return A new vector with the same values. */ - public Vector3 copy() { - return new Vector3(this.x, this.y, this.z); + public Point3D copy() { + return new Point3D(this.x, this.y, this.z); } /** @@ -34,8 +34,8 @@ public class Vector3 { * * @return The zero vector. */ - public static Vector3 zero() { - return new Vector3(0, 0, 0); + public static Point3D zero() { + return new Point3D(0, 0, 0); } /** @@ -52,10 +52,10 @@ public class Vector3 { * * @return The normalized vector. */ - public Vector3 normalize() { + public Point3D normalize() { double mag = this.magnitude(); if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector."); - return new Vector3(this.x / mag, this.y / mag, this.z / mag); + return new Point3D(this.x / mag, this.y / mag, this.z / mag); } /** @@ -64,8 +64,8 @@ public class Vector3 { * @param other The other vector to subtract. * @return The new vector. */ - public Vector3 subtract(Vector3 other) { - return new Vector3(this.x - other.x, this.y - other.y, this.z - other.z); + public Point3D subtract(Point3D other) { + return new Point3D(this.x - other.x, this.y - other.y, this.z - other.z); } /** @@ -74,8 +74,8 @@ public class Vector3 { * @param other The other vector to add. * @return The new vector. */ - public Vector3 add(Vector3 other) { - return new Vector3(this.x + other.x, this.y + other.y, this.z + other.z); + public Point3D add(Point3D other) { + return new Point3D(this.x + other.x, this.y + other.y, this.z + other.z); } /** @@ -84,8 +84,8 @@ public class Vector3 { * @param scalar The scalar to multiply by. * @return The multiplied vector. */ - public Vector3 multiply(double scalar) { - return new Vector3(this.x * scalar, this.y * scalar, this.z * scalar); + public Point3D multiply(double scalar) { + return new Point3D(this.x * scalar, this.y * scalar, this.z * scalar); } /** @@ -94,9 +94,9 @@ public class Vector3 { * @param scalar The scalar to divide by. * @return The divided vector. */ - public Vector3 divide(double scalar) { + public Point3D divide(double scalar) { if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero."); - return new Vector3(this.x / scalar, this.y / scalar, this.z / scalar); + return new Point3D(this.x / scalar, this.y / scalar, this.z / scalar); } /** @@ -104,8 +104,8 @@ public class Vector3 { * * @return The negated vector. */ - public Vector3 negate() { - return new Vector3(-this.x, -this.y, -this.z); + public Point3D negate() { + return new Point3D(-this.x, -this.y, -this.z); } /** @@ -116,7 +116,7 @@ public class Vector3 { * @param radZ Rotation around the Z axis in radians. * @return The rotated vector. */ - public Vector3 rotate(double radX, double radY, double radZ) { + public Point3D rotate(double radX, double radY, double radZ) { double cosX = Math.cos(radX); double cosY = Math.cos(radY); double cosZ = Math.cos(radZ); @@ -126,7 +126,7 @@ public class Vector3 { double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY; double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY; double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY; - return new Vector3(newX, newY, newZ); + return new Point3D(newX, newY, newZ); } /** @@ -135,7 +135,7 @@ public class Vector3 { * @param rotation The rotation vector. * @return The rotated vector. */ - public Vector3 rotate(Vector3 rotation) { + public Point3D rotate(Point3D rotation) { return rotate(rotation.x, rotation.y, rotation.z); } @@ -145,10 +145,10 @@ public class Vector3 { * @param angle Rotation around the X axis in radians. * @return The rotated vector. */ - public Vector3 rotateX(double angle) { + public Point3D rotateX(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x, y * cosTheta - z * sinTheta, y * sinTheta + z * cosTheta @@ -161,10 +161,10 @@ public class Vector3 { * @param angle Rotation around the Y axis in radians. * @return The rotated vector. */ - public Vector3 rotateY(double angle) { + public Point3D rotateY(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x * cosTheta + z * sinTheta, y, -x * sinTheta + z * cosTheta @@ -177,10 +177,10 @@ public class Vector3 { * @param angle Rotation around the Z axis in radians. * @return The rotated vector. */ - public Vector3 rotateZ(double angle) { + public Point3D rotateZ(double angle) { double sinTheta = Math.sin(angle); double cosTheta = Math.cos(angle); - return new Vector3( + return new Point3D( x * cosTheta - y * sinTheta, x * sinTheta + y * cosTheta, z @@ -193,7 +193,7 @@ public class Vector3 { * @param compare The other vector. * @return The squared distance between the two vectors. */ - public double distanceSq(Vector3 compare) { + public double distanceSq(Point3D compare) { return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2); } @@ -203,7 +203,7 @@ public class Vector3 { * @param compare The other vector. * @return The distance between the two vectors. */ - public double distance(Vector3 compare) { + public double distance(Point3D compare) { return Math.sqrt(distanceSq(compare)); } @@ -214,7 +214,7 @@ public class Vector3 { * @param lineEnd The ending point of the line. * @return The distance to the line. */ - public double distanceToLine(Vector3 lineStart, Vector3 lineEnd) { + public double distanceToLine(Point3D lineStart, Point3D lineEnd) { double lineDistance = lineStart.distanceSq(lineEnd); if (lineDistance == 0) return this.distanceSq(lineStart); @@ -225,7 +225,7 @@ public class Vector3 { t = Math.max(0, Math.min(1, t)); - return this.distanceSq(new Vector3( + return this.distanceSq(new Point3D( lineStart.x + t * (lineEnd.x - lineStart.x), lineStart.y + t * (lineEnd.y - lineStart.y), lineStart.z + t * (lineEnd.z - lineStart.z)) @@ -238,16 +238,16 @@ public class Vector3 { * @param vectors The list of vectors to compare. * @return The closest vector. */ - public Vector3 closest(Vector3 ... vectors) { + public Point3D closest(Point3D... vectors) { return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null); } - public Vector3 map(VectorMapFunction function) { + public Point3D map(VectorMapFunction function) { return function.apply(this); } public interface VectorMapFunction { - Vector3 apply(Vector3 vector); + Point3D apply(Point3D vector); } @Override 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 deleted file mode 100644 index 451de9d..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/GesturePath.java +++ /dev/null @@ -1,104 +0,0 @@ -package com.example.fitbot.util.processing; - -import java.util.ArrayList; -import java.util.List; - -public class GesturePath { - - // The vectors that make up the path. - private final Vector3[] vectors; - - public GesturePath(Vector3[] vectors) { - this.vectors = vectors; - } - - /** - * 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) { - // If there are no vectors, return 0. - if ( vectors.length == 0) - return 0; - - // If there's only one vector, return the distance to that vector. - if ( vectors.length == 1) - return vectors[0].distance(referencePoint); - - double distance = Double.MAX_VALUE; - double currentDistSq, nextDistSq; - int closestVectorIdx = 0; - - // Acquire two closest points to the reference point. - for ( int i = 0; i < vectors.length - 1; i++) { - currentDistSq = vectors[i].distanceSq(referencePoint); - nextDistSq = vectors[i + 1].distanceSq(referencePoint); - - if ( currentDistSq < distance) { - distance = currentDistSq; - closestVectorIdx = i; - } else if ( nextDistSq < distance) { - distance = nextDistSq; - closestVectorIdx = i + 1; - i++; // Skip the next iteration; this point is already closer. - } - } - - // Calculate the error between the two closest points. - Vector3 pointB = (closestVectorIdx == vectors.length - 1) ? - vectors[closestVectorIdx - 1] : // If the closest point is the last point, use the 1 to last one - (closestVectorIdx > 0 && // Find the closer point between the surrounding points. - (vectors[closestVectorIdx - 1].distanceSq(referencePoint) < vectors[closestVectorIdx + 1].distanceSq(referencePoint))) ? - vectors[closestVectorIdx - 1] : - vectors[closestVectorIdx + 1]; - - return referencePoint.distanceToLine(vectors[closestVectorIdx], pointB); - } - - // 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 2c10030..37e016f 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 @@ -1,11 +1,13 @@ package com.example.fitbot.util.processing; +import org.joml.Vector3f; + import java.util.Objects; public class MotionData { // Data of the motion sensor - public Vector3 acceleration, rotation; + public Vector3f acceleration, rotation; // Delimiter for the data received from the motion sensor private static final String DATA_DELIMITER = ";"; @@ -20,9 +22,9 @@ public class MotionData { * @param rotationY The rotation in the Y axis in degrees. * @param rotationZ The rotation in the Z axis in degrees. */ - public MotionData(double accelerationX, double accelerationY, double accelerationZ, double rotationX, double rotationY, double rotationZ) { - this.acceleration = new Vector3(accelerationX, accelerationY, accelerationZ); - this.rotation = new Vector3(rotationX, rotationY, rotationZ); + public MotionData(float accelerationX, float accelerationY, float accelerationZ, float rotationX, float rotationY, float rotationZ) { + this.acceleration = new Vector3f(accelerationX, accelerationY, accelerationZ); + this.rotation = new Vector3f(rotationX, rotationY, rotationZ); } /** @@ -31,7 +33,7 @@ public class MotionData { * @param acceleration The acceleration vector in m/s^2. * @param rotation The rotation vector in degrees. */ - public MotionData(Vector3 acceleration, Vector3 rotation) { + public MotionData(Vector3f acceleration, Vector3f rotation) { this.acceleration = acceleration; this.rotation = rotation; } @@ -52,12 +54,12 @@ public class MotionData { return null; return new MotionData( - Double.parseDouble(parts[0]), - Double.parseDouble(parts[1]), - Double.parseDouble(parts[2]), - Double.parseDouble(parts[3]), - Double.parseDouble(parts[4]), - Double.parseDouble(parts[5]) + Float.parseFloat(parts[0]), + Float.parseFloat(parts[1]), + Float.parseFloat(parts[2]), + Float.parseFloat(parts[3]), + Float.parseFloat(parts[4]), + Float.parseFloat(parts[5]) ); } } 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 7879807..edc8560 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 @@ -2,10 +2,12 @@ package com.example.fitbot.util.processing; import android.util.Log; +import com.example.fitbot.util.path.GesturePath; import com.example.fitbot.util.server.IWebSocketHandler; import com.example.fitbot.util.server.WebSocket; import org.jetbrains.annotations.NotNull; +import org.joml.Vector3f; import java.util.ArrayList; import java.util.List; @@ -16,9 +18,10 @@ public class MotionProcessor { public static final String DELIMITER = ";"; private final List preprocessedData = new ArrayList<>(); // Preprocessed motion data - 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 + private final List relativePath = new ArrayList<>(); // Relative path of the motion data + private Vector3f ZERO = new Vector3f(0, 0, 0); + + private float sampleRate = 1.0F; // samples/second private DataConsumer motionDataConsumer = (p1, p2, p3, p4) -> {}; private GesturePath path; private WebSocket socket; @@ -77,14 +80,14 @@ public class MotionProcessor { // Otherwise check if it starts with 'calibrate', this is the ZERO point. } else if ( data.startsWith("zero")) { // message to calibrate device String[] vectorData = data.split(" ")[1].split(DELIMITER); - ZERO = new Vector3( + ZERO = new Vector3f( Float.parseFloat(vectorData[0]), Float.parseFloat(vectorData[1]), Float.parseFloat(vectorData[2]) ); Log.i("MotionProcessor", "Device calibrated at " + ZERO.toString()); } else if ( data.startsWith("sampleRate")) { - this.sampleRate = Double.parseDouble(data.split(" ")[1]); + this.sampleRate = Float.parseFloat(data.split(" ")[1]); } } @@ -104,8 +107,8 @@ public class MotionProcessor { */ public void addMotionData(MotionData data) { preprocessedData.add(data); - Vector3 previous = this.relativePath.isEmpty() ? ZERO : this.relativePath.get(this.relativePath.size() - 1); - Vector3 relativeVector = getRelativeVector(data).add(previous); + Vector3f previous = this.relativePath.isEmpty() ? ZERO : this.relativePath.get(this.relativePath.size() - 1); + Vector3f relativeVector = getRelativeVector(data).add(previous); this.relativePath.add(relativeVector); motionDataConsumer.accept(relativeVector, data, this.relativePath.size(), this.sampleRate); } @@ -115,7 +118,7 @@ public class MotionProcessor { * * @param relativePath The new relative path. */ - public void setRelativePath(List relativePath) { + public void setRelativePath(List relativePath) { this.relativePath.clear(); this.relativePath.addAll(relativePath); } @@ -138,15 +141,17 @@ public class MotionProcessor { * @param motionData The motion data to calculate the relative vector for. * @return The relative vector of the motion data. */ - public Vector3 getRelativeVector(MotionData motionData) { + public Vector3f 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()) - .divide(2) - .multiply(sampleRate * sampleRate); + .rotateX(-motionData.rotation.x) + .rotateY(-motionData.rotation.y) + .rotateZ(-motionData.rotation.z) + .div(2) + .mul(sampleRate * sampleRate); } /** @@ -185,7 +190,7 @@ public class MotionProcessor { * @param referencePoint The reference point to compare the motion data to. * @return The error of the motion data compared to the reference path. */ - public double getError(GesturePath path, Vector3 referencePoint) + public double getError(GesturePath path, Vector3f referencePoint) { return path.getError(referencePoint); } @@ -197,7 +202,7 @@ public class MotionProcessor { * @param referencePoint The reference point to compare the path data to. * @return The error of the motion data compared to the reference path. */ - public double getError(Vector3 referencePoint) { + public double getError(Vector3f referencePoint) { if ( path == null) return 0; return path.getError(referencePoint); @@ -253,6 +258,6 @@ public class MotionProcessor { * @param sampleIndex The index of the current sample * @param sampleRate The sample rate. */ - void accept(Vector3 transformedVector, MotionData motionData, int sampleIndex, double sampleRate); + void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate); } } From 612b1c8a7cfb00ddf3c3526262f7bc215410cf15 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:29:22 +0200 Subject: [PATCH 05/11] bread --- code/src/Fitbot/.idea/misc.xml | 1 + .../PersonalMotionPreviewElement.java | 40 ++- .../example/fitbot/util/path/PathSegment.java | 105 ++----- .../com/example/fitbot/util/path/Point3D.java | 258 ------------------ .../src/main/res/layout/activity_fitness.xml | 10 + 5 files changed, 56 insertions(+), 358 deletions(-) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java create mode 100644 code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml diff --git a/code/src/Fitbot/.idea/misc.xml b/code/src/Fitbot/.idea/misc.xml index 135ab11..a67315e 100644 --- a/code/src/Fitbot/.idea/misc.xml +++ b/code/src/Fitbot/.idea/misc.xml @@ -22,6 +22,7 @@ + diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java index d70c88c..32631ee 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/components/PersonalMotionPreviewElement.java @@ -2,12 +2,12 @@ package com.example.fitbot.ui.components; import android.content.Context; import android.graphics.Canvas; +import android.graphics.Paint; import android.graphics.Path; import android.view.View; import com.example.fitbot.util.path.GesturePath; import com.example.fitbot.util.path.PathSegment; -import com.example.fitbot.util.path.Point3D; import com.example.fitbot.util.processing.MotionData; import com.example.fitbot.util.processing.MotionProcessor; @@ -22,6 +22,7 @@ public class PersonalMotionPreviewElement extends View { private double pathTime = 0.0D; // The timestamp at which the path is currently at. private MotionProcessor motionProcessor; private Path referencePath, performingPath; + private Paint referencePaint, performingPaint; /** * Constants for the preview path projection. @@ -45,22 +46,41 @@ public class PersonalMotionPreviewElement extends View { this.motionProcessor = new MotionProcessor(); this.motionProcessor.startListening(); this.motionProcessor.setMotionDataEventHandler((processed, preprocessed, sampleIndex, sampleRate) -> { - // TODO: Implement the calculation of the drawing path + // TODO: Implement the calculation of the `performingPath` based on the motion data }); - this.referencePath = generatePath() + this.referencePath = getDrawablePath(path.getSegments()); this.performingPath = new Path(); + + this.referencePaint = new Paint(); + this.referencePaint.setColor(-1); // White + this.referencePaint.setStyle(Paint.Style.STROKE); + this.referencePaint.setStrokeWidth(5.0f); + this.performingPaint = new Paint(); + this.performingPaint.setColor(0xFF0000FF); // Blue + this.performingPaint.setStyle(Paint.Style.STROKE); + this.performingPaint.setStrokeWidth(5.0f); } /** * Method that calculates the path that will be drawn on the * canvas. This method will be called every time new motion data is received. */ - private void calculateDrawingPath(Point3D transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { + private void calculateDrawingPath(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate) { // Recalculate the personal path based on the new motion data } + /** + * Method for setting the gesture path that will be drawn on the canvas. + * + * @param path The gesture path to draw. + */ + public void setGesturePath(GesturePath path) { + this.path = path; + this.referencePath = getDrawablePath(path.getSegments()); + } + /** * Method for setting the rotation of the preview path. * @@ -111,17 +131,12 @@ public class PersonalMotionPreviewElement extends View { * Method that converts a sequence of vectors to a Path object. * This path is a set of bezier curves that will be drawn on the canvas. * - * @param curvature The curvature of the bezier curves. - * This number must be between 0 and 1, and it represents - * by how much the path segments will be curved. - * A value of 0 represents no curvature at all, - * while values closer to 1 approach full circular curvature. * @param segments The path segments in the path. * These segments will be connected by bezier curves, which * all have unique curvature values. * @return The generated path object. */ - private Path getDrawablePath(double curvature, PathSegment... segments) { + private Path getDrawablePath(PathSegment... segments) { Path calculatedPath = new Path(); @@ -144,8 +159,7 @@ public class PersonalMotionPreviewElement extends View { @Override public void onDraw(Canvas canvas) { // Draw the sport preview canvas - - - + canvas.drawPath(referencePath, referencePaint); + canvas.drawPath(performingPath, performingPaint); } } diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java index 05f58a7..9f0c361 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/PathSegment.java @@ -4,11 +4,8 @@ import org.joml.Vector3f; public class PathSegment { - private final double curvature; - private final Vector3f start, end, normal; - private final double horizontalDistance; + private final Vector3f start, end; private final double distance; - private final double vectorAngle; // The angle of the vector from the start to the end point. /** * Constructor for creating a PathSegment of two lines, with the normal vector @@ -18,48 +15,8 @@ public class PathSegment { * @param end The end point of the line segment. */ public PathSegment(Vector3f start, Vector3f end) { - this.curvature = 0.0D; this.start = start; this.end = end; - this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); - this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); - this.distance = start.distance(end); - - // Normal vector calculation - double horizontalAngle = Math.atan2(end.z - start.z, end.x - start.x); - double verticalAngle = Math.atan2(end.y - start.y, horizontalDistance); - float sinVertical = (float)Math.sin(verticalAngle); - float cosVertical = (float)Math.cos(verticalAngle); - float sinHorizontal = (float)Math.sin(horizontalAngle); - float cosHorizontal = (float)Math.cos(horizontalAngle); - // The normal vector faces directly upward relative to the line between start and end. - // This means the normal vector is the perpendicular bisecting line from the line segment. - this.normal = new Vector3f( - sinVertical * cosHorizontal, - cosVertical, - sinVertical * sinHorizontal - ); - } - - /** - * Create a new path segment with a given start and end point. - * - * @param start The start point of the path segment. - * @param end The end point of the path segment. - * A value of 0 will result in the normal vector pointing upwards. - * @param normal The normal vector of the path segment. - * This vector determines how the line curves, if it has a positive curvature. - * @param curvature The curvature of the path segment. - * This value is between -1 and 1, and is used to determine the curvature of the path, - * around the normal vector. - */ - public PathSegment(Vector3f start, Vector3f end, Vector3f normal, double curvature) { - this.curvature = curvature; - this.start = start; - this.end = end; - this.horizontalDistance = Math.sqrt(Math.pow(end.x - start.x, 2) + Math.pow(end.z - start.z, 2)); - this.vectorAngle = Math.atan2(end.y - start.y, horizontalDistance); - this.normal = normal; this.distance = start.distance(end); } @@ -71,40 +28,9 @@ public class PathSegment { * @param dst The destination vector to interpolate to. * @param t The interpolation value between 0 and 1. */ - public void interpolate(Vector3f dst, double t) { - t = Math.min(1.0D, Math.max(0.0D, t)); // Ensure boundaries - - // If curvature is 0, use linear interpolation. - if (this.curvature == 0.0D) { - dst.set(start).lerp(end, (float) t); - } else { - // Interpolate over the ellipse. - double angle = t * Math.PI + vectorAngle; // Angle for on the ellipse - double cos = Math.cos(angle); - double sin = Math.sin(angle); - - double - - // Calculate the ellipse. - - - } - } - - /** - * Method for returning the control point of the path segment. - * This point is the point that the path segment curves around. - * - * @return The control point of the path segment. - */ - public Vector3f getControlPoint() { - return new Vector3f( - (this.start.x + this.end.x) / 2.0F, - (this.start.y + this.end.y) / 2.0F, - (this.start.z + this.end.z) / 2.0F - ) - .add(this.normal - .mul((float) this.curvature * (float) this.distance / 2.0F)); + public Vector3f interpolate(Vector3f dst, double t) { + return new Vector3f(this.start) + .lerp(this.end, (float) Math.min(1.0F, Math.max(0.0F, t))); } /** @@ -117,16 +43,21 @@ public class PathSegment { * @return The difference between the vector and the path segment. */ public double difference(Vector3f other) { - return 0.0D; // TODO: Implement. - } - /** - * Get the curvature of the path segment. - * - * @return The curvature of the path segment. - */ - public double getVectorAngle() { - return this.vectorAngle; + if (this.distance == 0) + return this.start.distance(other); + + double t = ((other.x - this.start.x) * (this.end.x - this.start.x) + + (other.y - this.start.y) * (this.end.y - this.start.y) + + (other.z - this.start.z) * (this.end.z - this.start.z)) / distance; + + t = Math.max(0, Math.min(1, t)); + + return other.distance(new Vector3f( + (float) (this.start.x + t * (this.end.x - this.start.x)), + (float) (this.start.y + t * (this.end.y - this.start.y)), + (float) (this.start.z + t * (this.end.z - this.start.z)) + )); } /** diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java deleted file mode 100644 index de14c78..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.example.fitbot.util.path; - -import java.util.Arrays; -import java.util.Comparator; - -public class Point3D { - - public double x, y, z; - - /** - * Constructor for creating a new vector. - * - * @param x The X component of the vector. - * @param y The Y component of the vector. - * @param z The Z component of the vector. - */ - public Point3D(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Copy the vector. - * - * @return A new vector with the same values. - */ - public Point3D copy() { - return new Point3D(this.x, this.y, this.z); - } - - /** - * Get the zero vector. - * - * @return The zero vector. - */ - public static Point3D zero() { - return new Point3D(0, 0, 0); - } - - /** - * Get the magnitude of the vector. - * - * @return The magnitude of the vector. - */ - public double magnitude() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - /** - * Normalize the vector. - * - * @return The normalized vector. - */ - public Point3D normalize() { - double mag = this.magnitude(); - if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector."); - return new Point3D(this.x / mag, this.y / mag, this.z / mag); - } - - /** - * Subtract the vector from another vector. - * - * @param other The other vector to subtract. - * @return The new vector. - */ - public Point3D subtract(Point3D other) { - return new Point3D(this.x - other.x, this.y - other.y, this.z - other.z); - } - - /** - * Add the vector to another vector. - * - * @param other The other vector to add. - * @return The new vector. - */ - public Point3D add(Point3D other) { - return new Point3D(this.x + other.x, this.y + other.y, this.z + other.z); - } - - /** - * Multiply the vector by a scalar. - * - * @param scalar The scalar to multiply by. - * @return The multiplied vector. - */ - public Point3D multiply(double scalar) { - return new Point3D(this.x * scalar, this.y * scalar, this.z * scalar); - } - - /** - * Divide the vector by a scalar. - * - * @param scalar The scalar to divide by. - * @return The divided vector. - */ - public Point3D divide(double scalar) { - if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero."); - return new Point3D(this.x / scalar, this.y / scalar, this.z / scalar); - } - - /** - * Negate the vector. - * - * @return The negated vector. - */ - public Point3D negate() { - return new Point3D(-this.x, -this.y, -this.z); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param radX Rotation around the X axis in radians. - * @param radY Rotation around the Y axis in radians. - * @param radZ Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotate(double radX, double radY, double radZ) { - double cosX = Math.cos(radX); - double cosY = Math.cos(radY); - double cosZ = Math.cos(radZ); - double sinX = Math.sin(radX); - double sinY = Math.sin(radY); - double sinZ = Math.sin(radZ); - double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY; - double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY; - double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY; - return new Point3D(newX, newY, newZ); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param rotation The rotation vector. - * @return The rotated vector. - */ - public Point3D rotate(Point3D rotation) { - return rotate(rotation.x, rotation.y, rotation.z); - } - - /** - * Rotate the vector around the X axis. - * - * @param angle Rotation around the X axis in radians. - * @return The rotated vector. - */ - public Point3D rotateX(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x, - y * cosTheta - z * sinTheta, - y * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Y axis. - * - * @param angle Rotation around the Y axis in radians. - * @return The rotated vector. - */ - public Point3D rotateY(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta + z * sinTheta, - y, - -x * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Z axis. - * - * @param angle Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotateZ(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta - y * sinTheta, - x * sinTheta + y * cosTheta, - z - ); - } - - /** - * 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(Point3D 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. - * - * @param compare The other vector. - * @return The distance between the two vectors. - */ - public double distance(Point3D compare) { - return Math.sqrt(distanceSq(compare)); - } - - /** - * Calculate the distance to a line defined by two points. - * - * @param lineStart The starting point of the line. - * @param lineEnd The ending point of the line. - * @return The distance to the line. - */ - public double distanceToLine(Point3D lineStart, Point3D lineEnd) { - double lineDistance = lineStart.distanceSq(lineEnd); - if (lineDistance == 0) - return this.distanceSq(lineStart); - - double t = ((this.x - lineStart.x) * (lineEnd.x - lineStart.x) + - (this.y - lineStart.y) * (lineEnd.y - lineStart.y) + - (this.z - lineStart.z) * (lineEnd.z - lineStart.z)) / lineDistance; - - t = Math.max(0, Math.min(1, t)); - - return this.distanceSq(new Point3D( - lineStart.x + t * (lineEnd.x - lineStart.x), - lineStart.y + t * (lineEnd.y - lineStart.y), - lineStart.z + t * (lineEnd.z - lineStart.z)) - ); - } - - /** - * Retrieve the closest vector to this one given a list of vectors. - * - * @param vectors The list of vectors to compare. - * @return The closest vector. - */ - public Point3D closest(Point3D... vectors) { - return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null); - } - - public Point3D map(VectorMapFunction function) { - return function.apply(this); - } - - public interface VectorMapFunction { - Point3D apply(Point3D vector); - } - - @Override - public String toString() - { - return "Vector3(" + this.x + ", " + this.y + ", " + this.z + ")"; - } -} diff --git a/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml new file mode 100644 index 0000000..35990c8 --- /dev/null +++ b/code/src/Fitbot/app/src/main/res/layout/activity_fitness.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file From af9138b1ea25d72e6c386f779a43d8b9214e94af Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:34:17 +0200 Subject: [PATCH 06/11] store changes --- .idea/workspace.xml | 31 +++++++++++++++---- .../motion-tracking-system-analysis.md | 4 +-- 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/.idea/workspace.xml b/.idea/workspace.xml index 8668fe5..1beee62 100644 --- a/.idea/workspace.xml +++ b/.idea/workspace.xml @@ -14,10 +14,9 @@ - + - @@ -271,6 +288,8 @@ - \ No newline at end of file diff --git a/docs/documentation/research-questions/motion-tracking-system-analysis.md b/docs/documentation/research-questions/motion-tracking-system-analysis.md index bef7d99..9a349ea 100644 --- a/docs/documentation/research-questions/motion-tracking-system-analysis.md +++ b/docs/documentation/research-questions/motion-tracking-system-analysis.md @@ -46,5 +46,5 @@ acceleration vector `A(x, y, z)` and rotation vector `R(x, y, z)` to an accelera perpendicular to the normal vector of the earth. This is because the acceleration vector of the device is relative to its own axes, and not to the earth's normal vector. To convert this, we'll have to multiply the acceleration vector `A(x, y, z)` by the rotation matrix -with negative angles, to rotate it back to be perpendicular with the normal of the earth. -After \ No newline at end of file +with negative angles, to rotate it back to be perpendicular with the normal of the earth. +After this transformation \ No newline at end of file From ce5f50439a64b9ff36a526fc2efd6b6f3fc0edda Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:40:35 +0200 Subject: [PATCH 07/11] fixed --- .../com/example/fitbot/util/path/Point3D.java | 258 ------------------ .../util/processing/IMotionDataConsumer.java | 14 + .../util/processing/MotionProcessor.java | 16 +- 3 files changed, 15 insertions(+), 273 deletions(-) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java deleted file mode 100644 index de14c78..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/Point3D.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.example.fitbot.util.path; - -import java.util.Arrays; -import java.util.Comparator; - -public class Point3D { - - public double x, y, z; - - /** - * Constructor for creating a new vector. - * - * @param x The X component of the vector. - * @param y The Y component of the vector. - * @param z The Z component of the vector. - */ - public Point3D(double x, double y, double z) { - this.x = x; - this.y = y; - this.z = z; - } - - /** - * Copy the vector. - * - * @return A new vector with the same values. - */ - public Point3D copy() { - return new Point3D(this.x, this.y, this.z); - } - - /** - * Get the zero vector. - * - * @return The zero vector. - */ - public static Point3D zero() { - return new Point3D(0, 0, 0); - } - - /** - * Get the magnitude of the vector. - * - * @return The magnitude of the vector. - */ - public double magnitude() { - return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); - } - - /** - * Normalize the vector. - * - * @return The normalized vector. - */ - public Point3D normalize() { - double mag = this.magnitude(); - if (mag == 0) throw new IllegalArgumentException("Cannot normalize the zero vector."); - return new Point3D(this.x / mag, this.y / mag, this.z / mag); - } - - /** - * Subtract the vector from another vector. - * - * @param other The other vector to subtract. - * @return The new vector. - */ - public Point3D subtract(Point3D other) { - return new Point3D(this.x - other.x, this.y - other.y, this.z - other.z); - } - - /** - * Add the vector to another vector. - * - * @param other The other vector to add. - * @return The new vector. - */ - public Point3D add(Point3D other) { - return new Point3D(this.x + other.x, this.y + other.y, this.z + other.z); - } - - /** - * Multiply the vector by a scalar. - * - * @param scalar The scalar to multiply by. - * @return The multiplied vector. - */ - public Point3D multiply(double scalar) { - return new Point3D(this.x * scalar, this.y * scalar, this.z * scalar); - } - - /** - * Divide the vector by a scalar. - * - * @param scalar The scalar to divide by. - * @return The divided vector. - */ - public Point3D divide(double scalar) { - if (scalar == 0) throw new IllegalArgumentException("Cannot divide by zero."); - return new Point3D(this.x / scalar, this.y / scalar, this.z / scalar); - } - - /** - * Negate the vector. - * - * @return The negated vector. - */ - public Point3D negate() { - return new Point3D(-this.x, -this.y, -this.z); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param radX Rotation around the X axis in radians. - * @param radY Rotation around the Y axis in radians. - * @param radZ Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotate(double radX, double radY, double radZ) { - double cosX = Math.cos(radX); - double cosY = Math.cos(radY); - double cosZ = Math.cos(radZ); - double sinX = Math.sin(radX); - double sinY = Math.sin(radY); - double sinZ = Math.sin(radZ); - double newX = x * cosY * cosZ + y * cosY * sinZ - z * sinY; - double newY = x * (sinX * sinY * cosZ - cosX * sinZ) + y * (sinX * sinY * sinZ + cosX * cosZ) + z * sinX * cosY; - double newZ = x * (cosX * sinY * cosZ + sinX * sinZ) + y * (cosX * sinY * sinZ - sinX * cosZ) + z * cosX * cosY; - return new Point3D(newX, newY, newZ); - } - - /** - * Rotate the vector around the X, Y, and Z axes. - * - * @param rotation The rotation vector. - * @return The rotated vector. - */ - public Point3D rotate(Point3D rotation) { - return rotate(rotation.x, rotation.y, rotation.z); - } - - /** - * Rotate the vector around the X axis. - * - * @param angle Rotation around the X axis in radians. - * @return The rotated vector. - */ - public Point3D rotateX(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x, - y * cosTheta - z * sinTheta, - y * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Y axis. - * - * @param angle Rotation around the Y axis in radians. - * @return The rotated vector. - */ - public Point3D rotateY(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta + z * sinTheta, - y, - -x * sinTheta + z * cosTheta - ); - } - - /** - * Rotate the vector around the Z axis. - * - * @param angle Rotation around the Z axis in radians. - * @return The rotated vector. - */ - public Point3D rotateZ(double angle) { - double sinTheta = Math.sin(angle); - double cosTheta = Math.cos(angle); - return new Point3D( - x * cosTheta - y * sinTheta, - x * sinTheta + y * cosTheta, - z - ); - } - - /** - * 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(Point3D 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. - * - * @param compare The other vector. - * @return The distance between the two vectors. - */ - public double distance(Point3D compare) { - return Math.sqrt(distanceSq(compare)); - } - - /** - * Calculate the distance to a line defined by two points. - * - * @param lineStart The starting point of the line. - * @param lineEnd The ending point of the line. - * @return The distance to the line. - */ - public double distanceToLine(Point3D lineStart, Point3D lineEnd) { - double lineDistance = lineStart.distanceSq(lineEnd); - if (lineDistance == 0) - return this.distanceSq(lineStart); - - double t = ((this.x - lineStart.x) * (lineEnd.x - lineStart.x) + - (this.y - lineStart.y) * (lineEnd.y - lineStart.y) + - (this.z - lineStart.z) * (lineEnd.z - lineStart.z)) / lineDistance; - - t = Math.max(0, Math.min(1, t)); - - return this.distanceSq(new Point3D( - lineStart.x + t * (lineEnd.x - lineStart.x), - lineStart.y + t * (lineEnd.y - lineStart.y), - lineStart.z + t * (lineEnd.z - lineStart.z)) - ); - } - - /** - * Retrieve the closest vector to this one given a list of vectors. - * - * @param vectors The list of vectors to compare. - * @return The closest vector. - */ - public Point3D closest(Point3D... vectors) { - return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null); - } - - public Point3D map(VectorMapFunction function) { - return function.apply(this); - } - - public interface VectorMapFunction { - Point3D apply(Point3D vector); - } - - @Override - public String toString() - { - return "Vector3(" + this.x + ", " + this.y + ", " + this.z + ")"; - } -} diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java new file mode 100644 index 0000000..75def12 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java @@ -0,0 +1,14 @@ +package com.example.fitbot.util.processing; + +public interface IMotionDataConsumer { + + /** + * Function for accepting motion data and the transformed vector. + * @param transformedVector The transformed vector. + * @param motionData The input motion data. + * @param sampleIndex The index of the current sample + * @param sampleRate The sample rate. + */ + void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate); + +} \ No newline at end of file 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 edc8560..f79d129 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 @@ -22,7 +22,7 @@ public class MotionProcessor { private Vector3f ZERO = new Vector3f(0, 0, 0); private float sampleRate = 1.0F; // samples/second - private DataConsumer motionDataConsumer = (p1, p2, p3, p4) -> {}; + private IMotionDataConsumer motionDataConsumer = (p1, p2, p3, p4) -> {}; private GesturePath path; private WebSocket socket; @@ -245,19 +245,5 @@ public class MotionProcessor { Log.i("MotionProcessor", "Path length: " + relativePath.size()); Log.i("MotionProcessor", "Sample rate: " + sampleRate); Log.i("MotionProcessor", "Calibration point: " + ZERO.toString()); - - /** - * Interface that accepts motion data and the transformed vector. - */ - public interface DataConsumer { - - /** - * Function for accepting motion data and the transformed vector. - * @param transformedVector The transformed vector. - * @param motionData The input motion data. - * @param sampleIndex The index of the current sample - * @param sampleRate The sample rate. - */ - void accept(Vector3f transformedVector, MotionData motionData, int sampleIndex, double sampleRate); } } From 3ca982c36f62b984520a33a71a726f9509e4e713 Mon Sep 17 00:00:00 2001 From: SebasKoedam Date: Wed, 15 May 2024 16:41:32 +0200 Subject: [PATCH 08/11] feat: Add Bluetooth permissions and location permission to AndroidManifest.xml --- code/src/Fitbot/app/src/main/AndroidManifest.xml | 8 ++++---- .../com/example/fitbot/ui/activities/MainActivity.java | 4 ++-- .../research-questions/position-tracking-research.md | 6 +++++- docs/teamdocumentatie/sprint-reports/sprint-report-s1.md | 2 +- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/code/src/Fitbot/app/src/main/AndroidManifest.xml b/code/src/Fitbot/app/src/main/AndroidManifest.xml index 2c2784b..23755ba 100644 --- a/code/src/Fitbot/app/src/main/AndroidManifest.xml +++ b/code/src/Fitbot/app/src/main/AndroidManifest.xml @@ -4,6 +4,10 @@ + + + + - - diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java index 1ed3962..9b92474 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java @@ -1,4 +1,4 @@ -package com.example.fitbot; +package com.example.fitbot.ui.activities; import android.annotation.SuppressLint; import android.content.Intent; @@ -16,7 +16,7 @@ import com.example.fitbot.util.processing.GesturePath; import com.example.fitbot.util.processing.MotionProcessor; import com.example.fitbot.util.processing.Vector3; -public class MainScreen extends AppCompatActivity { +public class MainActivity extends AppCompatActivity { //Variables DrawerLayout drawerLayout; diff --git a/docs/documentation/research-questions/position-tracking-research.md b/docs/documentation/research-questions/position-tracking-research.md index 9aa5001..f9edfa8 100644 --- a/docs/documentation/research-questions/position-tracking-research.md +++ b/docs/documentation/research-questions/position-tracking-research.md @@ -55,7 +55,8 @@ The software of the system will consist of the following: The Wii Fit Board will be connected to Pepper using the Wiiboard-simple library. The library will be used to read the sensor data from the Wii Fit Board and transfer it to Pepper. The position tracking algorithm will process the sensor data and determine the user's position. -Challenge: +Challenge: + - Connecting to the wii fit board. It is not possible to connect directly to the Wii Fit Board, it is necessary to use a library that can interpret the data sent by the Wii Fit Board. - The Wii Fit Balance Board sends data in a specific format. To interpret this data, it's necessary to understand the format and how to convert it to a usable format. - The Wii Fit Balance Board uses Bluetooth 2.0 to communicate. Pepper uses Bluetooth 4.0 this means that there might be compatibility issues/latancy issues. @@ -79,6 +80,9 @@ To be added ## References [Wiiboard lib](https://code.google.com/archive/p/wiiboard-simple/wikis/Documentation.wiki) +https://advanti-lab.sb.dfki.de/?page_id=64 +https://github.com/paulburton/fitscales +https://github.com/micromu/WiiRemoteJ ## Appendices diff --git a/docs/teamdocumentatie/sprint-reports/sprint-report-s1.md b/docs/teamdocumentatie/sprint-reports/sprint-report-s1.md index a9120f1..61889f5 100644 --- a/docs/teamdocumentatie/sprint-reports/sprint-report-s1.md +++ b/docs/teamdocumentatie/sprint-reports/sprint-report-s1.md @@ -80,6 +80,6 @@ Wat moet er beter? Wat ging er goed? Welke SMART leerdoelen hebben jullie voor d We zijn samen gaan zitten voor de retrospective, hier uit is het volgende voort gekomen (we hebben gebruik gemaakt van the 4 L's): -![alt text](Sprint1Retro.png) +![Retro](Sprint1Retro.png) De leerdoelen zet iedereen in zijn eigen scorion formulier. \ No newline at end of file From 32020ce801b26ff3f088825817c8435a1a9bb583 Mon Sep 17 00:00:00 2001 From: SebasKoedam Date: Wed, 15 May 2024 16:42:21 +0200 Subject: [PATCH 09/11] feat: Add Java build configuration update to .vscode/settings.json --- .vscode/settings.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .vscode/settings.json diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..c5f3f6b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "interactive" +} \ No newline at end of file From 5b5934d9853f9483bc43da0ecdfea600523492bf Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:44:25 +0200 Subject: [PATCH 10/11] g --- .../com/example/fitbot/util/processing/IMotionDataConsumer.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java index 75def12..b09726d 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/IMotionDataConsumer.java @@ -1,5 +1,7 @@ package com.example.fitbot.util.processing; +import org.joml.Vector3f; + public interface IMotionDataConsumer { /** From a4a01ae9efa971d2fb35e63da616befab5f90314 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 15 May 2024 16:50:22 +0200 Subject: [PATCH 11/11] fixed issues --- .../app/src/main/java/com/example/fitbot/MainActivity.java | 0 .../com/example/fitbot/ui/activities/FitnessActivity.java | 4 +--- .../java/com/example/fitbot/ui/activities/MainActivity.java | 4 +++- .../com/example/fitbot/util/processing/MotionProcessor.java | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/MainActivity.java deleted file mode 100644 index e69de29..0000000 diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java index 9c5d999..196a65b 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/FitnessActivity.java @@ -1,7 +1,6 @@ -package com.example.yourapp; +package com.example.fitbot.ui.activities; import android.os.Bundle; -import androidx.appcompat.widget.Toolbar; import com.aldebaran.qi.sdk.QiContext; import com.aldebaran.qi.sdk.RobotLifecycleCallbacks; import com.aldebaran.qi.sdk.design.activity.RobotActivity; @@ -11,7 +10,6 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.activity_fitness); } @Override diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java index 49049d7..949e2b0 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/ui/activities/MainActivity.java @@ -9,6 +9,8 @@ import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.AppCompatActivity; import android.support.v7.widget.Toolbar; +import com.example.fitbot.R; + public class MainActivity extends AppCompatActivity { //Variables @@ -34,7 +36,7 @@ public class MainActivity extends AppCompatActivity { navigationView.bringToFront(); ActionBarDrawerToggle toggle=new - ActionBarDrawerToggle(this,drawerLayout,toolbar,R.string.navigation_drawer_open,R.string.navigation_drawer_close); + ActionBarDrawerToggle(this,drawerLayout,toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close); drawerLayout.addDrawerListener(toggle); toggle.syncState(); } 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 f79d129..9a04461 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 @@ -127,7 +127,7 @@ public class MotionProcessor { * Function for setting the motion data receiver. * @param consumer The consumer to set. */ - public void setMotionDataEventHandler(DataConsumer consumer) { + public void setMotionDataEventHandler(IMotionDataConsumer consumer) { if ( consumer != null) this.motionDataConsumer = consumer; }