Merge branch 'main' of ssh://gitlab.fdmci.hva.nl/propedeuse-hbo-ict/onderwijs/2023-2024/out-a-se-ti/blok-4/muupooviixee66

This commit is contained in:
SebasKoedam
2024-05-15 16:42:11 +02:00
12 changed files with 494 additions and 429 deletions

31
.idea/workspace.xml generated
View File

@@ -14,10 +14,9 @@
</configurations>
</component>
<component name="ChangeListManager">
<list default="true" id="00599d5b-7eb5-44da-ad7f-98bf42384c16" name="Changes" comment="Renamed files, added motion-tracking-system-analysis.md">
<list default="true" id="00599d5b-7eb5-44da-ad7f-98bf42384c16" name="Changes" comment="Updated onderzoek-voorbeeld.md &amp; motion-tracking-system-analysis.md">
<change beforePath="$PROJECT_DIR$/.idea/workspace.xml" beforeDir="false" afterPath="$PROJECT_DIR$/.idea/workspace.xml" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/documentation/research-questions/motion-tracking-system-analysis.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/documentation/research-questions/motion-tracking-system-analysis.md" afterDir="false" />
<change beforePath="$PROJECT_DIR$/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md" beforeDir="false" afterPath="$PROJECT_DIR$/docs/personal-documentation/Luca/literatuuronderzoek/onderzoek-voorbeeld.md" afterDir="false" />
</list>
<option name="SHOW_DIALOG" value="false" />
<option name="HIGHLIGHT_CONFLICTS" value="true" />
@@ -144,7 +143,7 @@
<component name="SharedIndexes">
<attachedChunks>
<set>
<option value="bundled-js-predefined-1d06a55b98c1-2e7d6887c066-JavaScript-WS-241.15989.47" />
<option value="bundled-js-predefined-1d06a55b98c1-91d5c284f522-JavaScript-WS-241.15989.105" />
</set>
</attachedChunks>
</component>
@@ -164,7 +163,9 @@
<workItem from="1715686350378" duration="91000" />
<workItem from="1715687332786" duration="822000" />
<workItem from="1715724270673" duration="5481000" />
<workItem from="1715765990621" duration="4442000" />
<workItem from="1715765990621" duration="8538000" />
<workItem from="1715777647522" duration="725000" />
<workItem from="1715779408605" duration="3840000" />
</task>
<task id="LOCAL-00001" summary="Changes">
<created>1713528225837</created>
@@ -242,7 +243,23 @@
<option name="project" value="LOCAL" />
<updated>1715767477412</updated>
</task>
<option name="localTasksCounter" value="11" />
<task id="LOCAL-00011" summary="Updated onderzoek-voorbeeld.md and motion-tracking-system-analysis.md">
<option name="closed" value="true" />
<created>1715773120402</created>
<option name="number" value="00011" />
<option name="presentableId" value="LOCAL-00011" />
<option name="project" value="LOCAL" />
<updated>1715773120402</updated>
</task>
<task id="LOCAL-00012" summary="Updated onderzoek-voorbeeld.md &amp; motion-tracking-system-analysis.md">
<option name="closed" value="true" />
<created>1715780873394</created>
<option name="number" value="00012" />
<option name="presentableId" value="LOCAL-00012" />
<option name="project" value="LOCAL" />
<updated>1715780873394</updated>
</task>
<option name="localTasksCounter" value="13" />
<servers />
</component>
<component name="TypeScriptGeneratedFilesManager">
@@ -271,6 +288,8 @@
<MESSAGE value="onderzoek-voorbeeld.md" />
<MESSAGE value="Updated onderzoek-voorbeeld.md" />
<MESSAGE value="Renamed files, added motion-tracking-system-analysis.md" />
<option name="LAST_COMMIT_MESSAGE" value="Renamed files, added motion-tracking-system-analysis.md" />
<MESSAGE value="Updated onderzoek-voorbeeld.md and motion-tracking-system-analysis.md" />
<MESSAGE value="Updated onderzoek-voorbeeld.md &amp; motion-tracking-system-analysis.md" />
<option name="LAST_COMMIT_MESSAGE" value="Updated onderzoek-voorbeeld.md &amp; motion-tracking-system-analysis.md" />
</component>
</project>

View File

@@ -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'

View File

@@ -1,7 +1,6 @@
package com.example.fitbot.ui.activities;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.os.Bundle;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
@@ -9,12 +8,6 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.widget.Button;
import com.example.fitbot.R;
import com.example.fitbot.util.processing.GesturePath;
import com.example.fitbot.util.processing.MotionProcessor;
import com.example.fitbot.util.processing.Vector3;
public class MainActivity extends AppCompatActivity {

View File

@@ -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
}
}

View File

@@ -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<Vector3f> vectors;
/**
* Constructor for the Builder object.
*
* @param vectors The list of vectors to add.
*/
public Builder(List<Vector3f> 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]));
}
}
}

View File

@@ -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;
}
}

View File

@@ -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<Vector3> vectors;
/**
* Constructor for the Builder object.
*
* @param vectors The list of vectors to add.
*/
public Builder(List<Vector3> 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]));
}
}
}

View File

@@ -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);
}

View File

@@ -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])
);
}
}

View File

@@ -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,10 +18,11 @@ public class MotionProcessor {
public static final String DELIMITER = ";";
private final List<MotionData> preprocessedData = new ArrayList<>(); // Preprocessed motion data
private final List<Vector3> 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 DataConsumer motionDataConsumer = (p1, p2, p3, p4) -> {};
private final List<Vector3f> 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 IMotionDataConsumer 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<Vector3> relativePath) {
public void setRelativePath(List<Vector3f> 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);
@@ -240,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(Vector3 transformedVector, MotionData motionData, int sampleIndex, double sampleRate);
}
}

View File

@@ -1,258 +0,0 @@
package com.example.fitbot.util.processing;
import java.util.Arrays;
import java.util.Comparator;
public class Vector3 {
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 Vector3(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 Vector3 copy() {
return new Vector3(this.x, this.y, this.z);
}
/**
* Get the zero vector.
*
* @return The zero vector.
*/
public static Vector3 zero() {
return new Vector3(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 Vector3 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);
}
/**
* Subtract the vector from another vector.
*
* @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);
}
/**
* Add the vector to another vector.
*
* @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);
}
/**
* Multiply the vector by a scalar.
*
* @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);
}
/**
* Divide the vector by a scalar.
*
* @param scalar The scalar to divide by.
* @return The divided vector.
*/
public Vector3 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);
}
/**
* Negate the vector.
*
* @return The negated vector.
*/
public Vector3 negate() {
return new Vector3(-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 Vector3 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 Vector3(newX, newY, newZ);
}
/**
* Rotate the vector around the X, Y, and Z axes.
*
* @param rotation The rotation vector.
* @return The rotated vector.
*/
public Vector3 rotate(Vector3 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 Vector3 rotateX(double angle) {
double sinTheta = Math.sin(angle);
double cosTheta = Math.cos(angle);
return new Vector3(
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 Vector3 rotateY(double angle) {
double sinTheta = Math.sin(angle);
double cosTheta = Math.cos(angle);
return new Vector3(
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 Vector3 rotateZ(double angle) {
double sinTheta = Math.sin(angle);
double cosTheta = Math.cos(angle);
return new Vector3(
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(Vector3 compare) {
return Math.pow(compare.x - x, 2) + Math.pow(compare.y - y, 2) + Math.pow(compare.z - z, 2);
}
/**
* Get the distance between this vector and another vector.
*
* @param compare The other vector.
* @return The distance between the two vectors.
*/
public double distance(Vector3 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(Vector3 lineStart, Vector3 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 Vector3(
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 Vector3 closest(Vector3 ... vectors) {
return Arrays.stream(vectors).min(Comparator.comparingDouble(this::distanceSq)).orElse(null);
}
public Vector3 map(VectorMapFunction function) {
return function.apply(this);
}
public interface VectorMapFunction {
Vector3 apply(Vector3 vector);
}
@Override
public String toString()
{
return "Vector3(" + this.x + ", " + this.y + ", " + this.z + ")";
}
}

View File

@@ -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
with negative angles, to rotate it back to be perpendicular with the normal of the earth.
After this transformation