From 4dad284ba0becce4f4b35e61d0593f0e7270263c Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Tue, 4 Jun 2024 16:58:06 +0200 Subject: [PATCH 1/9] Updated documentation --- .../Luca/diagram-pepper-abstraction.md | 88 +++++++++++++++++++ .../Luca/expert-review-tips.md | 9 -- .../Luca/expert-review.md | 11 +++ .../Luca/infrastructure-design.md | 31 +++++++ 4 files changed, 130 insertions(+), 9 deletions(-) create mode 100644 docs/personal-documentation/Luca/diagram-pepper-abstraction.md delete mode 100644 docs/personal-documentation/Luca/expert-review-tips.md create mode 100644 docs/personal-documentation/Luca/infrastructure-design.md diff --git a/docs/personal-documentation/Luca/diagram-pepper-abstraction.md b/docs/personal-documentation/Luca/diagram-pepper-abstraction.md new file mode 100644 index 0000000..c92bbbc --- /dev/null +++ b/docs/personal-documentation/Luca/diagram-pepper-abstraction.md @@ -0,0 +1,88 @@ +## Pepper Abstraction Design + +--- + +### Introduction + +The Pepper robot is a complex system that can be controlled by a variety of different actions. To make the system more +manageable, we've decided implement abstraction and encapsulation in the classes related to Pepper controls. +This way, we can easily add new action events in the future. +All these classes inherit from the `AbstractPepperActionEvent` class. + +### Problems + +1. The Pepper robot functions with a system that only allows one action to be executed at a time, per action category. +This means that, for example, when two speech actions are executed at the same time, the application will crash due +to a `RuntimeException` being thrown. Due to this fact, whenever the execution of multiple processes overlap, +the application will crash. + +2. Besides the first problem, for the Pepper robot to be able to execute any actions, it is required to have a +QiContext available. This context is only provided in a class that extends the `RobotLifecycleCallbacks` class. +This means, that whenever the class does not extend this class, the robot will be unable to execute any actions. + +### Solution + +To prevent the application from crashing, we've decided to implement a queue system in the `Pepper` class. +This system allows us to queue any new actions that need to be executed whenever another action is already +being executed. This way, we can prevent the application throwing a `RuntimeException` and thus crashing. + +To tackle the second problem, we've decided to implement a system where the Pepper class has a global variable, which +holds the current QiContext. This means, that whenever a user decides to execute an action, and no current QiContext +is available, the action will be queued until a QiContext is available. This means that we can queue several actions +at once without any exceptions being thrown. + +### Diagrams + +#### Class Diagram + +```mermaid +classDiagram + class Pepper { + -pepperActionEventQueue : ConcurrentLinkedQueue + -isAnimating : AtomicBoolean + -isSpeaking : AtomicBoolean + + +latestContext : QiContext + +addToEventQueue(AbstractPepperActionEvent event) + +provideQiContext(QiContext context) + + -processEventQueue() + } + class AbstractPepperActionEvent { + +getAction() EPepperAction + } + class PepperSpeechEvent { + +phrase : String + +locale : Locale + +PepperSpeechEvent(String phrase, Locale locale) + +getSay(QiContext context) Say + } + class PepperAnimationEvent { + +PepperAnimationEvent(String animationName) + +PepperAnimationEvent(String animationName, IAnimationCompletedListener listener) + +getAnimation(QiContext context) Animate + +animationName : String + +IAnimationCompletedListener : IAnimationCompletedListener + } + Pepper <|-- AbstractPepperActionEvent + PepperSpeechEvent <|-- AbstractPepperActionEvent + PepperAnimationEvent <|-- AbstractPepperActionEvent +``` + +#### Queue System in Pepper class + +```mermaid + +graph LR + subgraph "Pepper Class - Action Queue System" + speak[say(String phrase)\nPublic\nCreate PepperSpeechEvent] --Call method--> addQueue + animate[animate(String animationName)\nPublic\nCreate PepperAnimationEvent] --Call method--> addQueue + addQueue[addToEventQueue(AbstractPepperActionEvent event)\nPublic\nAdd provided event to event queue] --Add to queue--> queue[Event Queue\nPrivate\nQueue containing all events that\nneed to be executed] + + addQueue --Call method--> handleQueue[processEventQueue()\nPrivate\nCheck whether there is a context\navailable, and whether an event\nis currently being executed.\nExecutes the next event in the Queue] + + queue <.-> handleQueue + + provideCtx[provideQiContext(QiContext context)\nPublic\nSets global QiContext variable\nto provided context. If the context \nis not null,process the event queue] --Sets global QiContext variable--> handleQueue + end +``` \ No newline at end of file diff --git a/docs/personal-documentation/Luca/expert-review-tips.md b/docs/personal-documentation/Luca/expert-review-tips.md deleted file mode 100644 index fb147bc..0000000 --- a/docs/personal-documentation/Luca/expert-review-tips.md +++ /dev/null @@ -1,9 +0,0 @@ - -## Expert review #1 - -### Document as you go -Documenteer alle problemen die voorkomen bij het project en noteer de -oplossingen voor deze problemen. Dit kan bijvoorbeeld d.m.v. een command die -cache files verwijderd, of op welke manier je een project fixt. Dit kan toekomstige -problemen voorkomen. - diff --git a/docs/personal-documentation/Luca/expert-review.md b/docs/personal-documentation/Luca/expert-review.md index fa98e9b..cf97502 100644 --- a/docs/personal-documentation/Luca/expert-review.md +++ b/docs/personal-documentation/Luca/expert-review.md @@ -12,6 +12,9 @@ Deze classes inheriten `AbstractPepperActionEvent`. K2: Gebruikers Test +( Maak een fictief persoon die de applicatie zou kunnen gebruiken, om erachter te komen +wat ze zouden willen ) + --- Wij hebben gezamenlijk gecommuniceerd over het plan, echter is alleen @@ -32,6 +35,14 @@ Zie bestand 'infrastructure.md' K4 - Ontwerp embedded system +Documenteer het queue systeem van de Pepper class +Maak een mermaid graph LR diagram + --- Zie '/documentation/hardware/sensors' + +K5 - Embedded Software Schrijven + +Feedback: +- Is in principe K1, diff --git a/docs/personal-documentation/Luca/infrastructure-design.md b/docs/personal-documentation/Luca/infrastructure-design.md new file mode 100644 index 0000000..b327d75 --- /dev/null +++ b/docs/personal-documentation/Luca/infrastructure-design.md @@ -0,0 +1,31 @@ +### Infrastructure Design + +--- + +```mermaid + +graph TB + subgraph "Raspberry Pi" + server[NodeJS Server\nHandles requests for\nexercises] + db[Database] + server --Fetch exercise--> db + db --Exercise--> server + + end + + subgraph "Pepper Robot" + webServer[Web Server\n\nHandles incoming rotational data\nfrom ESP8266] + motionProcessor[Motion Processor\n\nProcesses rotational data,\ncompares it to the current exercise\nand shows the statistics on the screen] + motionProcessor --Send HTTP GET for Exercise--> server + server --Send exercise in JSON format--> motionProcessor + webServer --Process rotational data--> motionProcessor + end + + subgraph "Motion Sensing Device" + esp[ESP8266\n\nMeasures sensor data\nand sends it to the web server] + gyro[Gyroscope\n\nMeasures rotational data\nand sends it to the ESP8266] + esp --Send rotational data\nto Pepper Web Server--> webServer + gyro <--> esp + end + +``` \ No newline at end of file From 1e3388a2c417fe82f8bdd39f92abac0a0df2fe11 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Tue, 4 Jun 2024 17:10:11 +0200 Subject: [PATCH 2/9] Updated documentation --- .../Luca/infrastructure-design.md | 25 +++++++++++++------ 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/docs/personal-documentation/Luca/infrastructure-design.md b/docs/personal-documentation/Luca/infrastructure-design.md index b327d75..e2bfc50 100644 --- a/docs/personal-documentation/Luca/infrastructure-design.md +++ b/docs/personal-documentation/Luca/infrastructure-design.md @@ -2,30 +2,41 @@ --- +As for our project, we've made the following design choices for our infrastructure. +We've decided to implement a NodeJS server on a Raspberry Pi, which will handle the requests for retrieving exercises. +This server will communicate with a MariaDB database, which contains the exercise data. +The Pepper robot will host a web server, which will handle the incoming rotational data from an ESP8266. +This data will then be processed by a motion processor class, `InputProcessor`, which will compare the rotational data +to the data of the current exercise and show how well the user is performing. + +Down below is a visual representation of how this infrastructure will look like. + ```mermaid graph TB subgraph "Raspberry Pi" - server[NodeJS Server\nHandles requests for\nexercises] - db[Database] - server --Fetch exercise--> db - db --Exercise--> server + server[NodeJS Server\n\nHandles requests for\nretrieving exercises] + db[Database - MariaDB\n\nContains exercise data] + server --Fetch database entry--> db + db --Return retrieved entry--> server end subgraph "Pepper Robot" webServer[Web Server\n\nHandles incoming rotational data\nfrom ESP8266] motionProcessor[Motion Processor\n\nProcesses rotational data,\ncompares it to the current exercise\nand shows the statistics on the screen] + ui[User Interface\n\nShows the current exercise,\nhow to perform it and the\nstatistics of the user's performance] motionProcessor --Send HTTP GET for Exercise--> server - server --Send exercise in JSON format--> motionProcessor + server --Send exercise data\nin JSON format--> motionProcessor webServer --Process rotational data--> motionProcessor + motionProcessor --Show statistics\non the UI--> ui end subgraph "Motion Sensing Device" esp[ESP8266\n\nMeasures sensor data\nand sends it to the web server] - gyro[Gyroscope\n\nMeasures rotational data\nand sends it to the ESP8266] + gyro[Gyroscope\n\nMeasures rotational data\n(Rx, Ry, Rz)] esp --Send rotational data\nto Pepper Web Server--> webServer - gyro <--> esp + gyro <---> esp end ``` \ No newline at end of file From cac78aecb87b36c70b8d4715050e909096b52904 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Tue, 4 Jun 2024 17:27:27 +0200 Subject: [PATCH 3/9] Updated documentation --- .../Luca/gebruikers-onderzoek-personage.md | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 docs/personal-documentation/Luca/gebruikers-onderzoek-personage.md diff --git a/docs/personal-documentation/Luca/gebruikers-onderzoek-personage.md b/docs/personal-documentation/Luca/gebruikers-onderzoek-personage.md new file mode 100644 index 0000000..12b4c35 --- /dev/null +++ b/docs/personal-documentation/Luca/gebruikers-onderzoek-personage.md @@ -0,0 +1,72 @@ +### Gebruikersonderzoek Personage + +--- + +Gezien de huidige omstandigheden is het nogal lastig om actuele feedback te verkrijgen +van onze doelgroep. Dit betekent dat we een fictief karakter moeten ontwikkelen die als onze +gebruiker functioneert. Hierbij moeten we zo veel mogelijk parameters vaststellen die +overeen kunnen komen met een potentiele gebruiker. + +Om hiermee te beginnen is het noodzakelijk om eerst deze parameters vast te stellen. + +Deze zijn als volgt: + +- Bedraagt een leeftijd van tussen de 50 en 70 jaar +- Woont in een verzorgingstehuis +- Heeft enigzins last van eenzaamheid +- Heeft moeite met lichamelijke activiteiten +- Grote kans op slecht zicht + +Nu deze parameters vastgesteld zijn kunnen we een fictief personage ontwikkelen die als onze +gebruiker functioneert. Dit personage zal de naam 'Henk' dragen. + +Vervolgens is het handig om de applicatie te introduceren. +De applicatie zal als volgt geintroduceerd worden: + +"Onze applicatie is een interactief programma waarmee u samen fitness activiteiten kunt +verrichten met een virtuele assistent genaamd Pepper. Pepper zal u begeleiden door de +verschillende oefeningen en u helpen met het uitvoeren van de oefeningen. Iedere +oefening zal worden begeleid door een stem die u verteld wat u moet doen en hoe u dit +moet doen. Daarnaast zal Pepper u ook feedback geven over hoe goed u de oefeningen +uitvoert." + +Een fictieve reactie op alle gestelde voorwaarden kan zijn als volgt: + +#### Introductie + +Goedendag, ik ben Henk en ik ben 67 jaar oud. Ik woon al een aantal jaar in verzorgingstehuis genaamd Amstelhuis. +Helaas heb ik de laatste tijd wat meer last van mijn gezondheid, waardoor ik minder goed kan bewegen. Hierdoor voel ik +me soms wat eenzaam en verveel ik me wel eens. + +Ik ben altijd erg actief geweest, dus toen ik hoorde over de Pepper FitBot app was ik meteen enthousiast. De +verpleegster vertelde me dat de Pepper app misschien wel kan helpen om mijn conditie op peil te houden en om me minder +eenzaam te voelen. Dus ik ben erg benieuwd wat de FitBot app allemaal kan! + +#### Gebruikservaring + +Eerste indruk: Toen ik de FitBot app voor het eerst gebruikte, vond ik het erg leuk dat Pepper me begeleidde door de +oefeningen. Hij is duidelijk en behulpzaam, en hij maakt er een gezellige sfeer van. + +##### Gebruiksgemak +De FitBot app is erg gebruiksvriendelijk. De app is eenvoudig te bedienen en de instructies zijn +duidelijk. Ook vind ik het fijn dat de app mijn voortgang bijhoudt. + +##### Functionaliteit +De FitBot app heeft veel leuke en nuttige functies. Ik vind het vooral leuk dat er verschillende +oefeningen zijn voor verschillende niveaus. + +##### Motivatie +Pepper is een geweldige motivator. Hij moedigt me aan om door te gaan en hij geeft me complimenten als ik +goed bezig ben. Hierdoor ben ik gemotiveerder om te blijven sporten. + +##### Verbeterpunten + +- Het zou leuk zijn als er meer oefeningen aan de app worden toegevoegd. Zo zou ik graag meer oefeningen willen doen voor +mijn spierkracht en lenigheid. +- De uitleg van de oefeningen is soms een beetje kort. Het zou fijn zijn als deze wat uitgebreider zou zijn. +- Het zou leuk zijn als er de mogelijkheid was om samen met anderen te sporten via de app. Zo zou ik bijvoorbeeld met +mijn kleinkinderen kunnen videobellen terwijl we allebei dezelfde oefeningen doen. + +Al met al ben ik erg tevreden over de FitBot app. Het is een leuke en effectieve manier om te sporten. Pepper is een +fijne motivator en de app heeft veel nuttige functies. Ik zou de FitBot app zeker aanbevelen aan andere mensen in het +verzorgingstehuis. \ No newline at end of file From ef7e7ae55c3cf08f93ed7535f94dc2f8482cf1df Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Tue, 4 Jun 2024 17:40:02 +0200 Subject: [PATCH 4/9] Updated infrastructure-design.md --- .../Luca/infrastructure-design.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/docs/personal-documentation/Luca/infrastructure-design.md b/docs/personal-documentation/Luca/infrastructure-design.md index e2bfc50..b2d3e89 100644 --- a/docs/personal-documentation/Luca/infrastructure-design.md +++ b/docs/personal-documentation/Luca/infrastructure-design.md @@ -11,6 +11,7 @@ to the data of the current exercise and show how well the user is performing. Down below is a visual representation of how this infrastructure will look like. +### General Infrastructure Diagram ```mermaid graph TB @@ -39,4 +40,22 @@ graph TB gyro <---> esp end +``` + +### Database Diagram + +For the design of our database, we've decided to only add a single table named `Exercise`. +This table contains all the information needed for the exercises. +```mermaid +classDiagram + class Exercise { + +ExerciseId : INT + +Name : VARCHAR + +Description : VARCHAR + +ShortDescription : VARCHAR + +ImageURL : VARCHAR + +VideoURL : VARCHAR + +MuscleGroup : VARCHAR + +Path : VARCHAR + } ``` \ No newline at end of file From f69fbe8e5b2001fb14136d1ae24daae892b10689 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 5 Jun 2024 11:29:32 +0200 Subject: [PATCH 5/9] Updated InputProcessor's convertRecordedDataToString to JSON output, updated GesturePath -> AnglePath --- .../com/example/fitbot/exercise/Exercise.java | 11 +- .../fitbot/exercise/ExerciseManager.java | 6 +- .../fitbot/ui/activities/FitnessActivity.java | 12 +- .../fitbot/ui/activities/MainActivity.java | 1 + .../example/fitbot/util/path/AnglePath.java | 63 +++++++++ .../example/fitbot/util/path/GesturePath.java | 126 ------------------ .../util/processing/InputProcessor.java | 97 ++++++++------ .../com/example/fitbot/PathSegmentTest.java | 37 ----- 8 files changed, 132 insertions(+), 221 deletions(-) create mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java delete mode 100644 code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java delete mode 100644 code/src/Fitbot/app/src/test/java/com/example/fitbot/PathSegmentTest.java diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java index 18153d7..7444f2b 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/Exercise.java @@ -1,12 +1,12 @@ package com.example.fitbot.exercise; -import com.example.fitbot.util.path.GesturePath; +import com.example.fitbot.util.path.AnglePath; public class Exercise { public final EMuscleGroup muscleGroup; - public final GesturePath leftPath; - public final GesturePath rightPath; + public final AnglePath leftPath; + public final AnglePath rightPath; public final String name; public final String shortDescription; public final String description; @@ -26,7 +26,10 @@ public class Exercise { * @param imageUrl The URL of the image. * @param videoUrl The URL of the video. */ - public Exercise(EMuscleGroup muscleGroup, String name,String shortDescription, String description, String imageUrl, String videoUrl, GesturePath leftPath, GesturePath rightPath, float exerciseTimeInSeconds) { + public Exercise(EMuscleGroup muscleGroup, String name, String shortDescription, + String description, String imageUrl, String videoUrl, + AnglePath leftPath, AnglePath rightPath, float exerciseTimeInSeconds) { + this.name = name; this.muscleGroup = muscleGroup; this.shortDescription = shortDescription; diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java index 0de72d0..8a9750e 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java @@ -1,6 +1,6 @@ package com.example.fitbot.exercise; -import com.example.fitbot.util.path.GesturePath; +import com.example.fitbot.util.path.AnglePath; import com.google.gson.JsonObject; import com.google.gson.JsonParser; @@ -118,8 +118,8 @@ public class ExerciseManager { content.get(PROPERTY_DESC).getAsString(), content.get(PROPERTY_IMAGE_URL).getAsString(), content.get(PROPERTY_VIDEO_URL).getAsString(), - GesturePath.fromString(leftRightData[0]), - GesturePath.fromString(leftRightData[1]), + AnglePath.fromString(leftRightData[0]), + AnglePath.fromString(leftRightData[1]), DEFAULT_SEGMENT_SPEED ); } catch (Exception e) { 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 7282278..c6a89ab 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 @@ -3,13 +3,10 @@ package com.example.fitbot.ui.activities; import android.app.Dialog; import android.content.Context; import android.graphics.drawable.ColorDrawable; -import android.media.MediaPlayer; -import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.widget.TextView; import android.view.View; -import android.view.WindowManager; import android.widget.Button; import android.widget.VideoView; @@ -102,7 +99,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall exerciseStatusElement.post(() -> { this.fetchExerciseAsync((exercise) -> { // Acquire paths from the exercise and provide them to the motion processor - Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getVectors(), exercise.rightPath.getVectors()}; + Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getAngleVectors(), exercise.rightPath.getAngleVectors()}; motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE); @@ -147,12 +144,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall // Set a listener to repeat the video while (EXERCISE_REP > 1) { - videoView.setOnCompletionListener(new MediaPlayer.OnCompletionListener() { - @Override - public void onCompletion(MediaPlayer mp) { - videoView.start(); // start the video again - } - }); + videoView.setOnCompletionListener(mp -> videoView.start()); // start the video again); EXERCISE_REP--; } }); 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 3aaca38..934a9df 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 @@ -16,6 +16,7 @@ import android.view.WindowManager; import android.widget.Button; import com.example.fitbot.R; +import com.example.fitbot.pepper.Pepper; import com.example.fitbot.util.NavigationManager; import java.io.OutputStream; diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java new file mode 100644 index 0000000..7df8ef9 --- /dev/null +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java @@ -0,0 +1,63 @@ +package com.example.fitbot.util.path; + +import org.joml.Vector3f; + +public class AnglePath { + + // The angles + private final Vector3f[] angles; + + /** + * Create a new gesture path with a given set of vectors and curvature. + * + * @param angles The vectors that make up the path. + */ + public AnglePath(Vector3f[] angles) { + this.angles = angles; + } + + /** + * Method for retrieving the vectors of the GesturePath. + */ + public Vector3f[] getAngleVectors() { + return this.angles; + } + + /** + * Function for converting a string to a GesturePath object. + * The input string bytes will be directly converted into 3d vectors. + * Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector. + *

+ * Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first). + * + * @param input The string to convert + * @return The GesturePath object + */ + + public static AnglePath fromString(String input) { + if (input == null) + throw new IllegalArgumentException("Input string cannot be null"); + byte[] bytes = input.getBytes(); + + // Check if the input string contains a valid amount of bytes (12 bytes per vector) + if (input.length() % 12 != 0) { + throw new IllegalArgumentException("Invalid input string length (" + input.length() + " bytes provided - must be a multiple of 12)"); + } + Vector3f[] vectors = new Vector3f[input.length() / 12]; + + float[] xyz = new float[3]; + for (int i = 0; i < bytes.length; i += 12) { + for (int j = 0; j < 3; j++) { + + xyz[j] = Float.intBitsToFloat( + (bytes[i + j * 4] & 0xFF) << 24 | + (bytes[i + j * 4 + 1] & 0xFF) << 16 | + (bytes[i + j * 4 + 2] & 0xFF) << 8 | + (bytes[i + j * 4 + 3] & 0xFF) + ); + } + vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]); + } + return new AnglePath(vectors); + } +} 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 deleted file mode 100644 index f3b8cb3..0000000 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/GesturePath.java +++ /dev/null @@ -1,126 +0,0 @@ -package com.example.fitbot.util.path; - -import org.joml.Vector3f; - -public class GesturePath { - - // The vectors that make up the path. - private final PathSegment[] segments; - - /** - * Create a new gesture path with a given set of vectors and curvature. - * - * @param vectors The vectors that make up the path. - */ - public GesturePath(Vector3f[] vectors) { - if (vectors.length < 2) { - this.segments = new PathSegment[1]; - this.segments[0] = new PathSegment( - new Vector3f(0, 0, 0), - new Vector3f(0, 0, 0) - ); - return; - } - - 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; - } - - /** - * Getter method for retrieving the path segments of this GesturePath. - * - * @return The path segments. - */ - public PathSegment[] getSegments() { - return segments; - } - - /** - * Method for retrieving the vectors of the GesturePath. - */ - public Vector3f[] getVectors() { - Vector3f[] vectors = new Vector3f[segments.length + 1]; - vectors[0] = segments[0].getStart(); - for (int i = 0; i < segments.length; i++) - vectors[i + 1] = segments[i].getEnd(); - - return vectors; - } - - /** - * 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]; - - PathSegment closest = segments[0]; - for (int i = 1; i < segments.length; i++) - closest = PathSegment.closer(closest, segments[i], reference); - - return closest; - } - - /** - * 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. - } - - - /** - * Function for converting a string to a GesturePath object. - * The input string bytes will be directly converted into 3d vectors. - * Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector. - *

- * Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first). - * - * @param input The string to convert - * @return The GesturePath object - */ - - public static GesturePath fromString(String input) { - if (input == null) - throw new IllegalArgumentException("Input string cannot be null"); - byte[] bytes = input.getBytes(); - - // Check if the input string contains a valid amount of bytes (12 bytes per vector) - if (input.length() % 12 != 0) { - throw new IllegalArgumentException("Invalid input string length (" + input.length() + " bytes provided - must be a multiple of 12)"); - } - Vector3f[] vectors = new Vector3f[input.length() / 12]; - - float[] xyz = new float[3]; - for (int i = 0; i < bytes.length; i += 12) { - for (int j = 0; j < 3; j++) { - - xyz[j] = Float.intBitsToFloat( - (bytes[i + j * 4] & 0xFF) << 24 | - (bytes[i + j * 4 + 1] & 0xFF) << 16 | - (bytes[i + j * 4 + 2] & 0xFF) << 8 | - (bytes[i + j * 4 + 3] & 0xFF) - ); - } - vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]); - } - return new GesturePath(vectors); - } -} diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java index e95008a..8e358b7 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/processing/InputProcessor.java @@ -2,12 +2,15 @@ package com.example.fitbot.util.processing; import android.util.Log; +import com.aldebaran.qi.sdk.object.geometry.Vector3; import com.example.fitbot.exercise.Exercise; import com.example.fitbot.exercise.ExerciseManager; import com.example.fitbot.util.server.WebServer; +import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.google.gson.JsonPrimitive; import org.jetbrains.annotations.NotNull; import org.joml.Vector3f; @@ -17,12 +20,15 @@ import java.util.List; public class InputProcessor { - private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data + private List[] selfRotationVectorPaths; // Relative path of the motion data + //private Vector3f[][] selfRotationVectorPaths; // Relative path of the motion data private Vector3f[][] targetRotationVectorPaths; // Target path of the motion data private final float sampleRate; // The sample rate of the motion sensor private float exerciseDurationInSeconds; + private float exerciseScore = 0.0F; + /** * This field is used to determine if the motion data is being recorded. * If this is the case, instead of functioning normally, the element @@ -45,7 +51,7 @@ public class InputProcessor { private IInputHandler motionDataConsumer; private static final String[] REQUIRED_SENSOR_JSON_PROPERTIES = - {"rotationX", "rotationY", "rotationZ", "type", "deviceId"}; + {"rotationX", "rotationY", "rotationZ", "deviceId"}; // The web server that listens for incoming motion data. private WebServer server; @@ -60,7 +66,9 @@ public class InputProcessor { * @param inputSampleRate The sample rate of the motion sensor. */ public InputProcessor(Vector3f[][] paths, float exerciseTime, float inputSampleRate) { - selfRotationVectorPaths = new Vector3f[paths.length][(int) (exerciseTime * inputSampleRate)]; + this.selfRotationVectorPaths = new ArrayList[2]; + this.selfRotationVectorPaths[0] = new ArrayList<>(); + this.selfRotationVectorPaths[1] = new ArrayList<>(); targetRotationVectorPaths = paths; this.sampleRate = inputSampleRate; @@ -78,10 +86,11 @@ public class InputProcessor { if ( this.recordingMovement ) throw new IllegalStateException("Cannot change exercise while recording movement."); - this.selfRotationVectorPaths = new Vector3f[2][(int) (exercise.exerciseTimeInSeconds * this.sampleRate)]; - this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getVectors().length]; - this.targetRotationVectorPaths[0] = exercise.leftPath.getVectors(); - this.targetRotationVectorPaths[1] = exercise.rightPath.getVectors(); + this.selfRotationVectorPaths[0] = new ArrayList<>(); + this.selfRotationVectorPaths[1] = new ArrayList<>(); + this.targetRotationVectorPaths = new Vector3f[2][exercise.rightPath.getAngleVectors().length]; + this.targetRotationVectorPaths[0] = exercise.leftPath.getAngleVectors(); + this.targetRotationVectorPaths[1] = exercise.rightPath.getAngleVectors(); this.exerciseDurationInSeconds = exercise.exerciseTimeInSeconds; this.secondsPassed = 0.0D; this.lastTime = System.currentTimeMillis(); @@ -101,7 +110,8 @@ public class InputProcessor { if (recording) { this.secondsPassed = 0.0D; this.lastTime = System.currentTimeMillis(); - this.selfRotationVectorPaths = new Vector3f[2][(int) (duration * this.sampleRate)]; + this.selfRotationVectorPaths[0] = new ArrayList<>(); + this.selfRotationVectorPaths[1] = new ArrayList<>(); } } @@ -161,7 +171,8 @@ public class InputProcessor { try { Log.i("MotionProcessor", "Time passed: " + this.secondsPassed + "s"); - Log.i("MotionProcessor", "Recording: " + this.recordingMovement + ", " + this.secondsPassed + " / " + this.recordingDurationInSeconds); + if ( this.recordingMovement) + Log.i("MotionProcessor", this.secondsPassed + " / " + this.recordingDurationInSeconds); Log.i("MotionProcessor", "Received packet data: " + data); JsonElement json = JsonParser.parseString(data); @@ -178,9 +189,11 @@ public class InputProcessor { } // Parse the data - Vector3f rotation = new Vector3f(object.get("rotationX").getAsFloat(), object.get("rotationY").getAsFloat(), object.get("rotationZ").getAsFloat()); + Vector3f rotation = new Vector3f( + object.get("rotationX").getAsFloat(), + object.get("rotationY").getAsFloat(), + object.get("rotationZ").getAsFloat()); int deviceId = object.get("deviceId").getAsInt(); - String type = object.get("type").getAsString(); // Parse the retrieved data parseRotationVector(rotation, deviceId); @@ -203,7 +216,7 @@ public class InputProcessor { // Supposed index of the current rotation vector in the `rotationVectorPaths` array - int selfIndex = (int) (secondsPassed * sampleRate); + this.selfRotationVectorPaths[deviceId].add(rotation); if ( this.recordingMovement && this.secondsPassed >= this.recordingDurationInSeconds) { // Do something with the recorded data. @@ -217,11 +230,8 @@ public class InputProcessor { Log.i("MotionProcessor", "Converted data: "); Log.i("MotionProcessor", converted); } - motionDataConsumer.accept(rotation, deviceId); - if (selfIndex >= selfRotationVectorPaths[deviceId].length || selfIndex < 0) - return; - selfRotationVectorPaths[deviceId][selfIndex] = rotation; + motionDataConsumer.accept(rotation, deviceId); } } @@ -234,38 +244,36 @@ public class InputProcessor { */ private String convertRecordedDataToString() { - // First, remove empty entries - StringBuilder pathBuilder = new StringBuilder(); - int[] intBits = new int[3]; char[] vectorChars = new char[12]; // 4 bytes per scalar, 12 chars per vector + JsonArray jsonArray = new JsonArray(); + /* + * Convert to JSON array in the following format: + * [ + * { "deviceId": number, "data": [ [x, y, z], [x, y, z], ... ] }, + * ] + */ // Iterate over all devices. In the current instance, it's 2. for ( int deviceId = 0; deviceId < selfRotationVectorPaths.length; deviceId++) { - for (Vector3f dataPoint : selfRotationVectorPaths[deviceId]) { - if (dataPoint != null) { - // Convert float to int bits for conversion to char - intBits[0] = Float.floatToIntBits(dataPoint.x); - intBits[1] = Float.floatToIntBits(dataPoint.y); - intBits[2] = Float.floatToIntBits(dataPoint.z); + JsonObject jsonDeviceObject = new JsonObject(); + jsonDeviceObject.addProperty("deviceId", deviceId); - // Convert int bits to char, in Big Endian order. - // This is important for converting back to float later. - for (int i = 0; i < 3; i++) { - vectorChars[i * 4] = (char) (intBits[i] >> 24); - vectorChars[i * 4 + 1] = (char) (intBits[i] >> 16); - vectorChars[i * 4 + 2] = (char) (intBits[i] >> 8); - vectorChars[i * 4 + 3] = (char) intBits[i]; - } + // Data array + JsonArray jsonDeviceDataArray = new JsonArray(); - pathBuilder.append(vectorChars); - } + for ( Vector3f vector : selfRotationVectorPaths[deviceId]) { + JsonArray jsonScalarArray = new JsonArray(); + jsonScalarArray.add(vector.x); + jsonScalarArray.add(vector.y); + jsonScalarArray.add(vector.z); + jsonDeviceDataArray.add(jsonScalarArray); } - // Add a separator between devices - if ( deviceId < selfRotationVectorPaths.length - 1) - pathBuilder.append(ExerciseManager.PATH_DELIMITER); + jsonDeviceObject.add("data", jsonDeviceDataArray); + + jsonArray.add(jsonDeviceObject); } - return pathBuilder.toString(); + return jsonArray.toString(); } /** @@ -288,6 +296,13 @@ public class InputProcessor { this.motionDataConsumer = consumer; } + /** + * Function for getting the combined (average) error value of both sensors. + public double getCombinedError() + { + + }*/ + /** * Function for getting the error offsets of the user's path compared to the * target path at a given point in time. @@ -300,7 +315,7 @@ public class InputProcessor { */ public double getError(int sensorId, float time) { - // Ensure the sensor ID is within the bounds of the array + /*// Ensure the sensor ID is within the bounds of the array if (sensorId < 0 || sensorId >= selfRotationVectorPaths.length) return 0.0d; @@ -316,7 +331,7 @@ public class InputProcessor { this.targetRotationVectorPaths[sensorId][targetIndex] != null ) { return this.selfRotationVectorPaths[sensorId][selfIndex].distance(this.targetRotationVectorPaths[sensorId][targetIndex]); - } + }*/ return 0.0d; } diff --git a/code/src/Fitbot/app/src/test/java/com/example/fitbot/PathSegmentTest.java b/code/src/Fitbot/app/src/test/java/com/example/fitbot/PathSegmentTest.java deleted file mode 100644 index 0ab0f1d..0000000 --- a/code/src/Fitbot/app/src/test/java/com/example/fitbot/PathSegmentTest.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.example.fitbot; - -import static org.junit.Assert.assertEquals; - -import com.example.fitbot.util.path.GesturePath; -import com.example.fitbot.util.path.PathSegment; - -import org.joml.Vector3f; -import org.junit.Test; - -public class PathSegmentTest { - - @Test - public void testPathSegment() { - // Test the PathSegment class - Vector3f[] vectors = new Vector3f[2]; - vectors[0] = new Vector3f(0, 0, 0); - vectors[1] = new Vector3f(1, 1, 1); - GesturePath path = new GesturePath(vectors); - PathSegment[] segments = path.getSegments(); - assertEquals(1, segments.length); - assertEquals(new Vector3f(0, 0, 0), segments[0].getStart()); - assertEquals(new Vector3f(1, 1, 1), segments[0].getEnd()); - assertEquals(new Vector3f(0.5f, 0.5f, 0.5f), segments[0].interpolate(0.5)); - } - - - @Test - public void test_pathSegmentInterpolation() { - Vector3f start = new Vector3f(0, 0, 0); - Vector3f end = new Vector3f(1, 1, 1); - PathSegment segment = new PathSegment(start, end); - assertEquals(new Vector3f(0.5f, 0.5f, 0.5f), segment.interpolate(0.5)); - } - - -} From 0410b23c0dc0fecbf01f17c31d296f6d9bff1b62 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 5 Jun 2024 11:30:44 +0200 Subject: [PATCH 6/9] Fixed typo in FitnessActivity --- .../java/com/example/fitbot/ui/activities/FitnessActivity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 4dc2472..e754d58 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 @@ -112,7 +112,7 @@ public class FitnessActivity extends RobotActivity implements RobotLifecycleCall exerciseStatusElement.post(() -> { this.fetchExerciseAsync((exercise) -> { // Acquire paths from the exercise and provide them to the motion processor - Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getVectors(), exercise.rightPath.getVectors()}; + Vector3f[][] vectors = new Vector3f[][]{exercise.leftPath.getAngleVectors(), exercise.rightPath.getAngleVectors()}; motionProcessor = new InputProcessor(vectors, exercise.exerciseTimeInSeconds, SENSOR_SAMPLE_RATE); From 4750b675f805b1d7d3bb99506dc44098363cca04 Mon Sep 17 00:00:00 2001 From: Sam Hos Date: Wed, 5 Jun 2024 11:37:50 +0200 Subject: [PATCH 7/9] Update documentation --- docs/documentation/diagrams/infrastructure.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/documentation/diagrams/infrastructure.md b/docs/documentation/diagrams/infrastructure.md index 267cdee..425d4f3 100644 --- a/docs/documentation/diagrams/infrastructure.md +++ b/docs/documentation/diagrams/infrastructure.md @@ -20,8 +20,9 @@ classDiagram Raspberry Pi --> NodeJS Raspberry Pi --> Database -NodeJS <--> Android Application : Request exercise data from database +NodeJS <--> Android Application : Request exercise data from database. Send ip adress to cache Database <--> NodeJS : Database queries +NodeJS --> ESP8266 : Get pepper ip ESP8266 --> Android Application : Send rotation data via WiFi to\n Pepper Web Server @@ -48,6 +49,7 @@ namespace Server { class NodeJS { +MariaDB +Handle requests + +Cache pepper IP } } From 69003476ed0267fb2a5d85265994eb4076178a5d Mon Sep 17 00:00:00 2001 From: Sam Hos Date: Wed, 5 Jun 2024 11:38:24 +0200 Subject: [PATCH 8/9] Added correct file name extensions --- .../brainstorm/{Infrastructure => Infrastructure.md} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename docs/documentation/brainstorm/{Infrastructure => Infrastructure.md} (100%) diff --git a/docs/documentation/brainstorm/Infrastructure b/docs/documentation/brainstorm/Infrastructure.md similarity index 100% rename from docs/documentation/brainstorm/Infrastructure rename to docs/documentation/brainstorm/Infrastructure.md From 20a28874015b30533cd5e9764a0b048a372c9386 Mon Sep 17 00:00:00 2001 From: Luca Warmenhoven Date: Wed, 5 Jun 2024 11:44:52 +0200 Subject: [PATCH 9/9] Added JSON parsing for path instead of binary decoding --- .../fitbot/exercise/ExerciseManager.java | 16 +++--- .../example/fitbot/util/path/AnglePath.java | 54 ++++++++++--------- 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java index 8a9750e..b69e4c0 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/exercise/ExerciseManager.java @@ -28,9 +28,6 @@ public class ExerciseManager { private static final String PROPERTY_PATH = "path"; private static final String PROPERTY_NAME = "name"; - // The delimiter used to separate the paths of the sensors. - public static final String PATH_DELIMITER = ":"; - public static final int SENSOR_COUNT = 2; private static final String[] REQUIRED_PROPERTIES = { @@ -40,8 +37,8 @@ public class ExerciseManager { }; public static final int DEFAULT_EXERCISE_REPETITIONS = 10; - public static final float DEFAULT_SEGMENT_SPEED = 1.0f; public static final float EXERCISE_ERROR_MARGIN = 1.0f; + public static final float EXERCISE_TIME_SCALING_FACTOR = 1.0f; /** * Function for sending an HTTP request to the server. @@ -104,9 +101,10 @@ public class ExerciseManager { // If one wants to add support for more sensors, one will have to adjust the Exercise // class to support more paths. System.out.println(content.get(PROPERTY_PATH).getAsString()); - String[] leftRightData = content.get(PROPERTY_PATH).getAsString().split(PATH_DELIMITER); - if (leftRightData.length != SENSOR_COUNT) { + AnglePath[] paths = AnglePath.fromString(content.get(PROPERTY_PATH).getAsString()); + + if (paths.length != SENSOR_COUNT) { System.out.println("Invalid path data."); return null; } @@ -118,9 +116,9 @@ public class ExerciseManager { content.get(PROPERTY_DESC).getAsString(), content.get(PROPERTY_IMAGE_URL).getAsString(), content.get(PROPERTY_VIDEO_URL).getAsString(), - AnglePath.fromString(leftRightData[0]), - AnglePath.fromString(leftRightData[1]), - DEFAULT_SEGMENT_SPEED + paths[0], + paths[1], + content.get(PROPERTY_EXERCISE_DURATION).getAsInt() ); } catch (Exception e) { e.printStackTrace(); diff --git a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java index 7df8ef9..1eb15e4 100644 --- a/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java +++ b/code/src/Fitbot/app/src/main/java/com/example/fitbot/util/path/AnglePath.java @@ -1,5 +1,11 @@ package com.example.fitbot.util.path; +import com.example.fitbot.exercise.ExerciseManager; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + import org.joml.Vector3f; public class AnglePath { @@ -25,39 +31,37 @@ public class AnglePath { /** * Function for converting a string to a GesturePath object. - * The input string bytes will be directly converted into 3d vectors. - * Every scalar is composed of 32 bits (4 characters), meaning 96 bits per vector. - *

- * Note: ASCII to Vector conversion is done in Big Endian format (most significant byte first). + * This function has been updated to convert Json strings to AnglePath objects. + * The JSON must be in the following format: + * [ + * { "deviceId": number, "data": [ [x, y, z], [x, y, z], ... ] }, + * ] * - * @param input The string to convert - * @return The GesturePath object + * @param jsonInput The string to convert + * @return The AnglePath object */ - public static AnglePath fromString(String input) { - if (input == null) + public static AnglePath[] fromString(String jsonInput) { + if (jsonInput == null) throw new IllegalArgumentException("Input string cannot be null"); - byte[] bytes = input.getBytes(); - // Check if the input string contains a valid amount of bytes (12 bytes per vector) - if (input.length() % 12 != 0) { - throw new IllegalArgumentException("Invalid input string length (" + input.length() + " bytes provided - must be a multiple of 12)"); - } - Vector3f[] vectors = new Vector3f[input.length() / 12]; + JsonElement parsed = JsonParser.parseString(jsonInput); + if (!parsed.isJsonArray()) + throw new IllegalArgumentException("Input string must be a JSON array"); - float[] xyz = new float[3]; - for (int i = 0; i < bytes.length; i += 12) { - for (int j = 0; j < 3; j++) { + if ( parsed.getAsJsonArray().size() != ExerciseManager.SENSOR_COUNT) + throw new IllegalArgumentException("Input string must contain 2 elements"); - xyz[j] = Float.intBitsToFloat( - (bytes[i + j * 4] & 0xFF) << 24 | - (bytes[i + j * 4 + 1] & 0xFF) << 16 | - (bytes[i + j * 4 + 2] & 0xFF) << 8 | - (bytes[i + j * 4 + 3] & 0xFF) - ); + Vector3f[][] angles = new Vector3f[ExerciseManager.SENSOR_COUNT][]; + for ( int dataArrayIdx = 0; dataArrayIdx < parsed.getAsJsonArray().size(); dataArrayIdx++) + { + JsonArray array = parsed.getAsJsonArray().get(dataArrayIdx).getAsJsonObject().get("data").getAsJsonArray(); + angles[dataArrayIdx] = new Vector3f[array.size()]; + for (int i = 0; i < array.size(); i++) { + JsonArray vec = array.get(i).getAsJsonArray(); + angles[dataArrayIdx][i] = new Vector3f(vec.get(0).getAsFloat(), vec.get(1).getAsFloat(), vec.get(2).getAsFloat()); } - vectors[i / 12] = new Vector3f(xyz[0], xyz[1], xyz[2]); } - return new AnglePath(vectors); + return new AnglePath[] {new AnglePath(angles[0]), new AnglePath(angles[1])}; } }