21 Commits 2845142baf ... 5314a6da53

Author SHA1 Message Date
  videowall-pc-user 5314a6da53 Display first point of every line 5 years ago
  Martin Edlund 409577c008 commented out assert 5 years ago
  Martin Edlund 2b127c2b2c Fixed appveyor yml 5 years ago
  Vincenz Mechler 0098967e2f fixed minor bugs in FileImporter 5 years ago
  Martin Edlund d5158d679d Added check for com5 5 years ago
  Vincenz Mechler 4856b1bdaa added try catch to cpp project to hopefully handle not connected motors when trying to initialize 5 years ago
  videowall-pc-user 59ed1ad6c7 minor changes 5 years ago
  Vincenz Mechler 7cd0ca0a0c made armband vibrate 5 years ago
  Martin Edlund (Laptop) 731bc1311e Merge branch 'develop' into userstory22 5 years ago
  m-edlund 4178ea6066 Deleted CustomCanvas 5 years ago
  m-edlund dc01cc6b94 Userstory20 (#17) 5 years ago
  Vincenz Mechler 6e2b672b2c added basic vibrotactile feedback for surface simulation 5 years ago
  Vincenz Mechler 02bb8cab48 completed StaticLibMotors dll (now representing a full interface to BodyActuator.dll), added documentation to staticLibMotors and added documentation to LocalArmbandInterface 5 years ago
  videowall-pc-user 7905509a6e removed parameters from actuate functions 5 years ago
  videowall-pc-user c70efe74f7 actuate works with 3 intensities: 100%, 66%, 33% 5 years ago
  Dennymany b1f1faf827 added function to delete existing armband 5 years ago
  Dennymany 9d3ad3a705 added actuate function 5 years ago
  Dennymany 02eaac3898 added actuate funtction to dll 5 years ago
  m-edlund f4674b5efd Userstory13 (#16) 5 years ago
  m-edlund aee756b8f8 Hotfix (#15) 5 years ago
  m-edlund e93039223e Userstory8 (#14) 5 years ago
42 changed files with 2477 additions and 1069 deletions
  1. 15 16
      .appveyor.yml
  2. 8 8
      Finished Userstories/userstory13.md
  3. 13 0
      Finished Userstories/userstory8.md
  4. 0 0
      Neues Textdokument.txt
  5. 3 0
      SketchAssistant/.gitignore
  6. 15 3
      SketchAssistant/GenerateCoverageReport.bat
  7. BIN
      SketchAssistant/NPTrackingTools.dll
  8. 0 450
      SketchAssistant/SketchAssistant.Tests/UnitTest1.cs
  9. 0 30
      SketchAssistant/SketchAssistant/test_input_files/all.svg
  10. 2 1
      SketchAssistant/SketchAssistantWPF/App.xaml
  11. 0 18
      SketchAssistant/SketchAssistantWPF/CustomCanvas.cs
  12. 4 0
      SketchAssistant/SketchAssistantWPF/DebugData.cs
  13. 38 36
      SketchAssistant/SketchAssistantWPF/FileImporter.cs
  14. 39 0
      SketchAssistant/SketchAssistantWPF/Frame.cs
  15. 149 0
      SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs
  16. 39 0
      SketchAssistant/SketchAssistantWPF/HighPerformanceTimer.cs
  17. 43 14
      SketchAssistant/SketchAssistantWPF/InternalLine.cs
  18. 97 6
      SketchAssistant/SketchAssistantWPF/LocalArmbandInterface.cs
  19. 404 103
      SketchAssistant/SketchAssistantWPF/MVP_Model.cs
  20. 290 94
      SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs
  21. 15 0
      SketchAssistant/SketchAssistantWPF/MVP_View.cs
  22. 76 37
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml
  23. 165 48
      SketchAssistant/SketchAssistantWPF/MainWindow.xaml.cs
  24. BIN
      SketchAssistant/SketchAssistantWPF/NPTrackingTools.dll
  25. 195 0
      SketchAssistant/SketchAssistantWPF/OptiTrackConnector.cs
  26. 1 4
      SketchAssistant/SketchAssistantWPF/SketchAction.cs
  27. 47 4
      SketchAssistant/SketchAssistantWPF/SketchAssistantWPF.csproj
  28. BIN
      SketchAssistant/SketchAssistantWPF/StaticLibMotors.dll
  29. 24 0
      SketchAssistant/SketchAssistantWPF/Wristband.cs
  30. BIN
      SketchAssistant/SketchAssistantWPF/nicubunu-Quill.ico
  31. BIN
      SketchAssistant/SketchAssistantWPF/optitrack_setup.ttp
  32. 3 2
      SketchAssistant/SketchAssistantWPF/packages.config
  33. 187 37
      SketchAssistant/StaticLib1/ArmbandInterface.cpp
  34. 62 13
      SketchAssistant/StaticLib1/ArmbandInterface.h
  35. 494 140
      SketchAssistant/WhiteTests/UITest.cs
  36. 4 2
      SketchAssistant/WhiteTests/WhiteTests.csproj
  37. 0 3
      SketchAssistant/WhiteTests/packages.config
  38. 3 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/H_V_C_S.svg
  39. 8 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/circle_h_v_c_s.svg
  40. 21 0
      SketchAssistant/WhiteTests/test_input_files/whitelisted/path2.svg
  41. BIN
      SketchAssistant/optitrack_setup.ttp
  42. 13 0
      userstory20.md

+ 15 - 16
.appveyor.yml

@@ -1,27 +1,26 @@
 image:
- - Visual Studio 2017
- 
+    - Visual Studio 2017
+
 install:
- - "nuget install TestStack.White"
- - "nuget install MSTest.TestAdapter"
- - "nuget install MSTest.TestFramework"
- - "nuget restore SketchAssistant/SketchAssistant.sln"
- 
+    - "nuget install TestStack.White"
+    - "nuget install MSTest.TestAdapter"
+    - "nuget install MSTest.TestFramework"
+    - "nuget restore SketchAssistant/SketchAssistant.sln"
+
 before_build:
- - ps: .\screenres.ps1
- 
+    - ps: .\screenres.ps1
+
 artifacts:
- - path: \SketchAssistant\GeneratedReports\
+    - path: \SketchAssistant\GeneratedReports\
 #  - path: SketchAssistant/WhiteTests/test_input_files/whitelisted/*.svg
 #    name: whitelisted svg files for testing
 #  - path: SketchAssistant/WhiteTests/test_input_files/blacklisted/*.svg
 #    name: blacklisted svg files for testing
 
 before_test:
- - ps: "SketchAssistant/GenerateCoverageReport.bat"
-
+    - ps: "SketchAssistant/GenerateCoverageReport.bat"
+#don't run tests depending on [DeploymentItem] and filesystem access
 test:
-    #don't run tests depending on [DeploymentItem] and filesystem access
-    categories:
-        except:
-            - FileIO
+ categories:
+  except:
+   - Local

+ 8 - 8
Finished Userstories/userstory13.md

@@ -1,13 +1,13 @@
-# Userstory 13 
+# Userstory 13 
  
 |**ID**|13|  
 |-|-|
-|**Name**|Start und Endpunkt anzeigen|
-|**Beschreibung**|Beim Nachzeichnen kann der Start- & Endpunkt einer nachzuzeichnenden Linie angzeigt werden.|
-|**Akzeptanzkriterium**|Sofern auf der linken Seite eine nachzeichenbare Grafik dargestellt wird, kann der Start- und Endpunkt der ersten Linie der linken Grafik an der richtigen Stelle auf der rechten Seite angezeigt werden.|
+|**Name**|Overlay Unterstützung|
+|**Beschreibung**|Als Nutzer möchte ich, dass beim Nachzeichnen, der Start- & Endpunkt einer nachzuzeichnenden Linie angzeigt werden kann, sowie bei Verwendung des Optitrack Systems ein Cursor angezeigt werden kann.|
+|**Akzeptanzkriterium**|Es ist möglich Overlay Elemente anzuzeigen und frei zu positionieren.|
 |Geschätzter Aufwand (Story Points)|2|
-|Entwickler|Martin|
-|Umgesetzt in Iteration|4|
-|Tatsächlicher Aufwand (Std.)|1|
-|Velocity (Std./Story Point)|0.5|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|15|
+|Tatsächlicher Aufwand (Std.)|8|
+|Velocity (Std./Story Point)|4|
 |Bemerkungen|Keine|

+ 13 - 0
Finished Userstories/userstory8.md

@@ -0,0 +1,13 @@
+# Userstory 8
+
+|**ID**|8|
+|-|-|
+|**Name**|Vergleich vom gezeichneten Bild mit der Ursprungsgrafik|
+|**Beschreibung**|Ein Feature, das die vom Nutzer gezeichnete Grafik, mit der geladenen Grafik vergleicht.|
+|**Akzeptanzkriterium**|Eine Funktion bei der Ähnlichkeit der vom Nutzer gezeichneten Zeichnung und der geladenen Grafik in der UI angezeigt wird.|
+|Geschätzter Aufwand (Story Points)|10|
+|Entwickler|Martin Edlund|
+|Umgesetzt in Iteration|14|
+|Tatsächlicher Aufwand (Std.)|12.5|
+|Velocity (Std./Story Point)|1.25|
+|Bemerkungen|Keine|

+ 0 - 0
Neues Textdokument.txt


+ 3 - 0
SketchAssistant/.gitignore

@@ -0,0 +1,3 @@
+/GeneratedReports/
+!*.ico
+!*.svg

+ 15 - 3
SketchAssistant/GenerateCoverageReport.bat

@@ -1,13 +1,25 @@
 if not exist "%~dp0GeneratedReports" mkdir "%~dp0GeneratedReports"
 
-"%~dp0\packages\OpenCover.4.7.922\tools\OpenCover.Console.exe" ^
+for /d %%a in (
+  "%~dp0\packages\OpenCover.*"
+) do set "openCoverFolder=%%~fa\"
+
+for /d %%a in (
+  "%~dp0\packages\Microsoft.TestPlatform.*"
+) do set "microPlat=%%~fa\"
+
+for /d %%a in (
+  "%~dp0\packages\ReportGenerator.*"
+) do set "repGen=%%~fa\"
+
+"%openCoverFolder%\tools\OpenCover.Console.exe" ^
 -register:user ^
--target:"%~dp0\packages\Microsoft.TestPlatform.16.0.0\tools\net451\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" ^
+-target:"%microPlat%\tools\net451\Common7\IDE\Extensions\TestPlatform\vstest.console.exe" ^
 -targetargs:"%~dp0\WhiteTests\bin\Debug\WhiteTests.dll" ^
 -filter:"+[SketchAssistantWPF*]*" ^
 -mergebyhash ^
 -output:"%~dp0\GeneratedReports\opencovertests.xml"
 
-"%~dp0\packages\ReportGenerator.4.0.14\tools\net47\ReportGenerator.exe" ^
+"%repGen%\tools\net47\ReportGenerator.exe" ^
 -reports:"%~dp0\GeneratedReports\opencovertests.xml" ^
 -targetdir:"%~dp0\GeneratedReports\ReportGeneratorOutput"

BIN
SketchAssistant/NPTrackingTools.dll


+ 0 - 450
SketchAssistant/SketchAssistant.Tests/UnitTest1.cs

@@ -1,450 +0,0 @@
-using System;
-using Microsoft.VisualStudio.TestTools.UnitTesting;
-using System.Drawing;
-using System.Collections.Generic;
-using SketchAssistant;
-using System.Windows.Forms;
-using System.IO;
-
-namespace Tests
-{
-    [TestClass]
-    public class LineTests
-    {
-        //========================//
-        //= Bresenham Line Tests =//
-        //========================//
-
-        [TestMethod]
-        public void BresenhamLineTest1()
-        {
-            //Test point
-            List<Point> expectedResult = new List<Point>();
-            expectedResult.Add(new Point(1, 2));
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(1, 2), new Point(1, 2));
-            Assert.AreEqual(1, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest2()
-        {
-            //Test line going from left to right
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 1; i <= 6; i++) { expectedResult.Add(new Point(i, 2)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(1, 2), new Point(6, 2));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest3()
-        {
-            //Test line going from right to left
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 6; i >= 1; i--) { expectedResult.Add(new Point(i, 2)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(6, 2), new Point(1, 2));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest4()
-        {
-            //Test line going from top to bottom
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 5; i <= 25; i++) { expectedResult.Add(new Point(7, i)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(7, 5), new Point(7, 25));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest5()
-        {
-            //Test line going from bottom to top
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 25; i >= 5; i--) { expectedResult.Add(new Point(7, i)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(7, 25), new Point(7, 5));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest6()
-        {
-            //Test exactly diagonal line from top left to bottom right
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 5; i <= 25; i++) { expectedResult.Add(new Point(i + 2, i)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(7, 5), new Point(27, 25));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        [TestMethod]
-        public void BresenhamLineTest7()
-        {
-            //Test exactly diagonal line from bottom right to top left
-            List<Point> expectedResult = new List<Point>();
-            for (int i = 25; i >= 5; i--) { expectedResult.Add(new Point(i + 2, i)); }
-            List<Point> actualResult = SketchAssistant.GeometryCalculator.BresenhamLineAlgorithm(new Point(27, 25), new Point(7, 5));
-            Assert.AreEqual(expectedResult.Count, actualResult.Count);
-            for (int i = 0; i < actualResult.Count; i++)
-            {
-                Assert.AreEqual(expectedResult[i], actualResult[i]);
-            }
-        }
-
-        //===========================//
-        //= Matrix Population Tests =//
-        //===========================//
-
-        [TestMethod]
-        public void MatrixTest1()
-        {
-            //Populate Matrix for temporary Line
-            List<Point> thePoints = new List<Point>();
-            thePoints.Add(new Point(1, 1));
-            thePoints.Add(new Point(1, 2));
-            bool[,] testBoolMatrix = new bool[5, 5];
-            List<int>[,] testLineMatrix = new List<int>[5, 5];
-            bool[,] resultBoolMatrix = new bool[5, 5];
-            HashSet<int>[,] resultLineMatrix = new HashSet<int>[5, 5];
-            Line testLine = new Line(thePoints);
-            testLine.PopulateMatrixes(resultBoolMatrix, resultLineMatrix);
-            for (int i = 0; i < 5; i++)
-            {
-                for (int j = 0; j < 5; j++)
-                {
-                    Assert.AreEqual(testBoolMatrix[i, j], resultBoolMatrix[i, j]);
-                    Assert.AreEqual(testLineMatrix[i, j], resultLineMatrix[i, j]);
-                }
-            }
-        }
-
-        [TestMethod]
-        public void MatrixTest2()
-        {
-            //Populate Matrix for non-temporary Line
-            List<Point> thePoints = new List<Point>();
-            thePoints.Add(new Point(1, 1));
-            thePoints.Add(new Point(3, 3));
-            bool[,] testBoolMatrix = new bool[5, 5];
-            HashSet<int>[,] testLineMatrix = new HashSet<int>[5, 5];
-            for (int i = 1; i <= 3; i++)
-            {
-                testBoolMatrix[i, i] = true;
-                HashSet<int> temp = new HashSet<int>();
-                temp.Add(5);
-                testLineMatrix[i, i] = temp;
-            }
-            bool[,] resultBoolMatrix = new bool[5, 5];
-            HashSet<int>[,] resultLineMatrix = new HashSet<int>[5, 5];
-            Line testLine = new Line(thePoints, 5);
-            testLine.PopulateMatrixes(resultBoolMatrix, resultLineMatrix);
-            for (int i = 0; i < 5; i++)
-            {
-                for (int j = 0; j < 5; j++)
-                {
-                    Assert.AreEqual(testBoolMatrix[i, j], resultBoolMatrix[i, j]);
-                    if (testLineMatrix[i, j] != null && resultLineMatrix[i, j] != null)
-                    {
-                        for (int k = 0; k < resultLineMatrix[i, j].Count; k++)
-                        {
-                            Assert.AreEqual(true, testLineMatrix[i, j].SetEquals(resultLineMatrix[i, j]));
-                        }
-                    }
-                }
-            }
-        }
-
-        //=========================//
-        //= Line Constructor Test =//
-        //=========================//
-
-        [TestMethod]
-        public void ConstructorTest()
-        {
-            //Create non-temporary Line and check points
-            //reference Points
-            List<Point> comparisonPoints = new List<Point> {new Point(2,2), new Point(3, 1), new Point(4, 1), new Point(5, 1), new Point(6, 2),
-                new Point(7, 3), new Point(8, 4), new Point(9, 5), new Point(10, 6), new Point(11, 5), new Point(11, 4), new Point(11, 3),
-                new Point(10, 2), new Point(9, 1), new Point(8, 2), new Point(7, 3), new Point(6, 4), new Point(5, 5), new Point(4, 5),
-                new Point(3, 5), new Point(2, 5), new Point(1, 4)};
-            //test Points, with intermediate points missing & duplicate points
-            List<Point> testPoints = new List<Point> {new Point(2,2), new Point(3, 1), new Point(5, 1), new Point(5, 1), new Point(5, 1),
-                new Point(8, 4), new Point(10, 6), new Point(11, 5), new Point(11, 3), new Point(9, 1), new Point(9, 1), new Point(9, 1),
-                new Point(5, 5), new Point(2, 5), new Point(2, 5), new Point(1, 4) };
-            Line testLine = new Line(testPoints, 0);
-            List<Point> returnedPoints = testLine.GetPoints();
-            Assert.AreEqual(comparisonPoints.Count, returnedPoints.Count);
-            for (int i = 0; i < returnedPoints.Count; i++)
-            {
-                Assert.AreEqual(comparisonPoints[i], returnedPoints[i]);
-            }
-        }
-    }
-
-    [TestClass]
-    public class ActionHistoryTests
-    {
-
-        private ActionHistory GetActionHistory()
-        {
-            return new ActionHistory();
-        }
-
-        [DataTestMethod]
-        [DataRow(SketchAction.ActionType.Start, 5, -1, "A new canvas was created.")]
-        [DataRow(SketchAction.ActionType.Draw, 5, 5, "Line number 5 was drawn.")]
-        [DataRow(SketchAction.ActionType.Delete, 10, 10, "Line number 10 was deleted.")]
-        public void ScetchActionTest1(SketchAction.ActionType type, int id, int exit, String response)
-        {
-            HashSet<int> actualResult = new HashSet<int>();
-            if (!type.Equals(SketchAction.ActionType.Start)) { actualResult.Add(id); }
-            SketchAction testAction = new SketchAction(type, id);
-            Assert.AreEqual(type, testAction.GetActionType());
-            Assert.AreEqual(true, actualResult.SetEquals(testAction.GetLineIDs()));
-            Assert.AreEqual(response, testAction.GetActionInformation());
-        }
-
-        [DataTestMethod]
-        [DataRow(SketchAction.ActionType.Start, 1, 2, 3, "A new canvas was created.")]
-        [DataRow(SketchAction.ActionType.Draw, 3, 3, 3, "Line number 3 was drawn.")]
-        [DataRow(SketchAction.ActionType.Delete, 20, 30, 40, "Several Lines were deleted.")]
-        public void ScetchActionTest2(SketchAction.ActionType type, int id1, int id2, int id3, String response)
-        {
-            HashSet<int> actualResult = new HashSet<int>();
-            if (!type.Equals(SketchAction.ActionType.Start))
-            {
-                actualResult.Add(id1);
-                actualResult.Add(id2);
-                actualResult.Add(id3);
-            }
-            SketchAction testAction = new SketchAction(type, actualResult);
-            Assert.AreEqual(type, testAction.GetActionType());
-            Assert.AreEqual(true, actualResult.SetEquals(testAction.GetLineIDs()));
-            Assert.AreEqual(response, testAction.GetActionInformation());
-        }
-
-        [DataTestMethod]
-        [DataRow(SketchAction.ActionType.Start, SketchAction.ActionType.Start, true)]
-        [DataRow(SketchAction.ActionType.Draw, SketchAction.ActionType.Delete, false)]
-        public void ActionHistoryTest1(SketchAction.ActionType action1, SketchAction.ActionType action2, bool isEmpty)
-        {
-            ActionHistory testHistory = GetActionHistory();
-            if (!action1.Equals(SketchAction.ActionType.Start)) { testHistory.AddNewAction(new SketchAction(action1, 5)); }
-            if (!action2.Equals(SketchAction.ActionType.Start)) { testHistory.AddNewAction(new SketchAction(action2, 5)); }
-            Assert.AreEqual(isEmpty, testHistory.IsEmpty());
-        }
-
-        [DataTestMethod]
-        [DataRow(SketchAction.ActionType.Draw, "Last Action: Line number 0 was drawn.")]
-        [DataRow(SketchAction.ActionType.Delete, "Last Action: Line number 0 was deleted.")]
-        public void ActionHistoryUndoRedoTest(SketchAction.ActionType actionType, String message)
-        {
-            ActionHistory testHistory = GetActionHistory();
-            SketchAction testAction = new SketchAction(actionType, 0);
-            testHistory.AddNewAction(testAction);
-            Assert.AreEqual(true, testHistory.CanUndo());
-            testHistory.MoveAction(true);
-            Assert.AreEqual(true, testHistory.CanRedo());
-            var lastActionLabel = testHistory.MoveAction(false);
-            Assert.AreEqual(actionType, testHistory.GetCurrentAction().GetActionType());
-            Assert.AreEqual(message, lastActionLabel);
-        }
-    }
-
-    [TestClass]
-    [DeploymentItem(@"SketchAssistant.Tests\test_input_files\")]
-    public class FileImporterTests
-    { 
-
-        /// <summary>
-        /// instance of TestContext to be able to access deployed files
-        /// </summary>
-         private TestContext testContextInstance;
-        /// <summary>
-        ///Gets or sets the test context which provides
-        ///information about and functionality for the current test run.
-        ///</summary>
-        public TestContext TestContext
-        {
-            get
-            {
-                return testContextInstance;
-            }
-            set
-            {
-                testContextInstance = value;
-            }
-        }
-        /*
-        [DataTestMethod]
-        [DataRow(new int[] { 54, 43, 57, 11, 145, 34, 113, 299, 0 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
-        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 })]
-        [DataRow(new int[] { 33, 42, 140, 30, 30, 30, 32, 145, 2 }, new int[] { 54, 43, 57, 11, 145, 34, 113, 199, 0 })]
-        public void ParseISADInputSuccessfulTest(int[] xCoordinates, int[] yCoordinates)
-        {
-            Form1 program = new Form1();
-            FileImporter uut = new SketchAssistant.FileImporter();
-
-            List<String> file = new List<string>();
-            file.Add("drawing");
-            file.Add("300x200");
-            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
-            {
-                file.Add("line");
-                file.Add(xCoordinates[i] + ";" + yCoordinates[i]);
-                file.Add(xCoordinates[i + 1] + ";" + yCoordinates[i + 1]);
-                file.Add(xCoordinates[i + 2] + ";" + yCoordinates[i + 2]);
-                file.Add("endline");
-            }
-            file.Add("enddrawing");
-
-            (int, int, List<Line>) values = uut.ParseISADInputForTesting(file.ToArray());
-            program.CreateCanvasAndSetPictureForTesting(values.Item1, values.Item2, values.Item3);
-
-            Line[] drawing = GetLeftImage(program).ToArray();
-
-            Assert.AreEqual(xCoordinates.Length / 3, drawing.Length);
-            for (int i = 0; i < xCoordinates.Length - 2; i += 3)
-            {
-                Point[] currentLine = drawing[i / 3].GetPoints().ToArray();
-                Assert.AreEqual(3, currentLine.Length);
-                for (int j = 0; j < 3; j++)
-                {
-                    Assert.IsTrue(currentLine[j].X == xCoordinates[i + j] && currentLine[j].Y == yCoordinates[i + j]);
-                }
-            }
-        }
-
-        [DataTestMethod]
-        [DataRow(new String[] {})]
-        [DataRow(new String[] { "begindrawing", "300x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300;300", "line", "50;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "30.5x300", "line", "50;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "line", "50;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "beginline", "50;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "500;50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50x50", "100;50", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50", "100", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "line", "endline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "stopline", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "enddrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline", "endrawing" })]
-        [DataRow(new String[] { "drawing", "300x300", "line", "50;50", "100;50", "endline" })]
-        public void ParseISADInputExceptionTest(String[] file)
-        {
-            bool exceptionThrown = false;
-            Form1 program = new Form1();
-            FileImporter uut = new SketchAssistant.FileImporter();
-            //check that left image initially is uninitialized
-            Assert.IsNull(GetLeftImage(program));
-            //initialize left image with a valid isad drawing
-            (int, int, List<Line>) values = uut.ParseISADInputForTesting(new string[] { "drawing", "300x205", "line", "40;40", "140;140", "endline", "enddrawing" });
-            program.CreateCanvasAndSetPictureForTesting(values.Item1, values.Item2, values.Item3);
-            //save left image for later comparison
-            List<Line> oldLeftImage = GetLeftImage(program);
-            try
-            {
-                //try to initialize the left image with an invalid isad drawing
-                (int, int, List<Line>) values1 = uut.ParseISADInputForTesting(file);
-                program.CreateCanvasAndSetPictureForTesting(values1.Item1, values1.Item2, values1.Item3);
-            }
-            catch(FileImporterException)
-            {
-                //save the occurence of an exception
-                exceptionThrown = true;
-            }
-            //check that an exception has been thrown
-            Assert.IsTrue(exceptionThrown);
-            //check that the left image has not been changed by the failed image import
-            Assert.AreEqual(oldLeftImage, GetLeftImage(program));
-        }
-
-        /// <summary>
-        /// local helper method retrieving the left image from a Form1 instance
-        /// </summary>
-        /// <returns>the left image of the given Form1 instance</returns>
-        private List<Line> GetLeftImage(Form1 program)
-        {
-            //cast is save as long as Form1#GetAllVariables() is conform to its contract
-            return (List<Line>) program.GetAllVariables()[4];
-        }
-        */
-
-        /// <summary>
-        /// parses all whitelisted files and ensures no exceptions are thrown (parsing abortion, e.g. due to corrupted input files, are realized by throwing a FileImporterException)
-        /// </summary>
-        [TestMethod]
-        public void parseSVGInputNoErrorForWhitelistedFilesTest()
-        {
-            FileImporter uut = new FileImporter();
-
-            string[] files = Directory.GetFiles(TestContext.DeploymentDirectory + @"\test_input_files\whitelisted", "*.svg", SearchOption.AllDirectories);
-            Assert.IsTrue(files.Length > 0);
-
-            foreach (string s in files) //parse each of the whitelisted files
-            {
-                bool noExceptionThrown = true;
-                try
-                {
-                    uut.ParseSVGInputFile(s, 10000, 10000);
-                }
-                catch (Exception e)
-                {
-                    noExceptionThrown = false;
-                }
-                Assert.IsTrue(noExceptionThrown);
-            }
-        }
-
-        /// <summary>
-        /// parses all blacklisted files and ensures an instance of FileIporterException is thrown for each file, but no other exceptions occur
-        /// </summary>
-        [TestMethod]
-        public void parseSVGInputNoErrorForBlacklistedFilesTest()
-        {
-            FileImporter uut = new FileImporter();
-
-            string[] files = Directory.GetFiles(TestContext.DeploymentDirectory + @"\test_input_files\blacklisted", "*.svg", SearchOption.AllDirectories);
-            Assert.IsTrue(files.Length > 0);
-            foreach (string s in files) //parse each of the blacklisted files
-            {
-                bool correctExceptionThrown = false;
-                try
-                {
-                    uut.ParseSVGInputFile(s, 10000, 10000);
-                }
-                catch(FileImporterException e)
-                {
-                    correctExceptionThrown = true;
-                }
-                catch(Exception e)
-                {
-                }
-                Assert.IsTrue(correctExceptionThrown);
-            }
-        }
-    }
-
-}

+ 0 - 30
SketchAssistant/SketchAssistant/test_input_files/all.svg

@@ -1,30 +0,0 @@
-fsfwefafaefcSC
-F
-S
-FSFewFAHBfAg
-<DSFVSF>
-<GSG FS02MAFfm="Fmseikf" />
-source: https://www.w3schools.com/
-<svg height="500" width="500">
-  <polygon points="220,10 300,210 170,250 123,234" style="fill:lime;stroke:purple;stroke-width:1" />
-  <polyline points="20,20 40,25 60,40 80,120 120,140 200,180"
-  style="fill:none;stroke:black;stroke-width:3" />
-    <line x1="0" y1="0" x2="200" y2="200" style="stroke:rgb(255,0,0);stroke-width:2" />
-  <rect x="50" y="20" width="150" height="150"
-  style="fill:blue;stroke:pink;stroke-width:5;fill-opacity:0.1;stroke-opacity:0.9" />
-  <rect x="352.324" y="311.765" fill="#5486F7" width="4" height="128"/>
-  <rect x="478.871" y="266.351" fill="#FFFFFF" width="12" height="12"/>
-  <ellipse cx="250" cy="250" rx="100" ry="50"
-  style="fill:yellow;stroke:purple;stroke-width:2" />
-  <ellipse cx="240" cy="100" rx="220" ry="30" style="fill:purple" />
-  <ellipse cx="220" cy="70" rx="190" ry="20" style="fill:lime" />
-  <ellipse cx="210" cy="45" rx="170" ry="15" style="fill:yellow" />
-  <path id="lineAB" d="M 100 350 l 150 -300" stroke="red"
-  stroke-width="3" fill="none" />
-  <path id="lineBC" d="M 250 50 l 150 300" stroke="red"
-  stroke-width="3" fill="none" />
-  <path d="M 175 200 l 150 0" stroke="green" stroke-width="3"
-  fill="none" />
-  <path d="M 100 350 q 150 -300 300 0" stroke="blue"
-  stroke-width="5" fill="none" />
-</svg>

+ 2 - 1
SketchAssistant/SketchAssistantWPF/App.xaml

@@ -2,7 +2,8 @@
              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
              xmlns:local="clr-namespace:SketchAssistantWPF"
-             StartupUri="MainWindow.xaml">
+             StartupUri="MainWindow.xaml"
+             ShutdownMode="OnMainWindowClose">
     <Application.Resources>
          
     </Application.Resources>

+ 0 - 18
SketchAssistant/SketchAssistantWPF/CustomCanvas.cs

@@ -1,18 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Threading.Tasks;
-using System.Windows.Automation.Peers;
-using System.Windows.Controls;
-
-namespace SketchAssistantWPF
-{
-    public class CustomCanvas : Canvas
-    {
-        protected override AutomationPeer OnCreateAutomationPeer()
-        {
-            return new FrameworkElementAutomationPeer(this);
-        }
-    }
-}

File diff suppressed because it is too large
+ 4 - 0
SketchAssistant/SketchAssistantWPF/DebugData.cs


+ 38 - 36
SketchAssistant/SketchAssistantWPF/FileImporter.cs

@@ -26,7 +26,7 @@ namespace SketchAssistantWPF
         /// <summary>
         /// array containing all characters interpreted as whitespaces which seperate words/tokens in the input file
         /// </summary>
-        readonly char[] whitespaces = new char[] { ' ' , ',' };
+        readonly char[] whitespaces = new char[] { ' ', ',' };
 
         /// <summary>
         /// number of points to create along the outline of an ellipse, divisible by 4
@@ -43,7 +43,7 @@ namespace SketchAssistantWPF
         /// </summary>
         /// <param name="fileName">the path of the input file</param>
         /// <returns>the width and height of the left canvas and the parsed picture as a list of lines</returns>
-public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
+        public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         {
             return ParseISADInput(System.IO.File.ReadAllLines(fileName));
         }
@@ -69,7 +69,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             }
             Tuple<int, int> dimensions = ParseISADHeader(allLines);
             List<InternalLine> picture = ParseISADBody(allLines, dimensions.Item1, dimensions.Item2);
-            
+
             return new Tuple<int, int, List<InternalLine>>(dimensions.Item1, dimensions.Item2, picture);
         }
 
@@ -197,13 +197,13 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             i++;
             int width; //width of the resulting picture in pixels
             int height; //height of the resulting picture in pixels
-            if(windowWidth != 0 && windowHeight != 0)
+            if (windowWidth != 0 && windowHeight != 0)
             {
                 if (windowWidth / windowHeight > sizedef.Item1 / sizedef.Item2) //height dominant, width has to be smaller than drawing window to preserve xy-scale
                 {
                     scale = windowHeight / sizedef.Item2;
                     height = (int)Math.Round(windowHeight);
-                    width = (int) Math.Round(scale * sizedef.Item1);
+                    width = (int)Math.Round(scale * sizedef.Item1);
                 }
                 else //width dominant, height has to be smaller than drawing window to preserve xy-scale
                 {
@@ -218,7 +218,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 width = sizedef.Item1;
                 height = sizedef.Item2;
             }
-            for (int j=0; j < allLines.Length; j++)
+            for (int j = 0; j < allLines.Length; j++)
             {
                 allLines[j] = allLines[j].Trim(whitespaces);
             }
@@ -238,9 +238,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 i++;
             }
             String[] currentLine = allLines[i].Split(' ');
-            int width= -1;
-            int height= -1;
-            for(int j= 0; j < currentLine.Length; j++)
+            int width = -1;
+            int height = -1;
+            for (int j = 0; j < currentLine.Length; j++)
             {
                 if (currentLine[j].StartsWith("width"))
                 {
@@ -251,9 +251,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                     height = Convert.ToInt32(ParseSingleSVGAttribute(currentLine[j]));
                 }
             }
-            if(width == -1)
+            if (width == -1)
             {
-                throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i+1);
+                throw new FileImporterException("missing width definition in SVG header", "the header should contain the \"width=...\" attribute", i + 1);
             }
             if (height == -1)
             {
@@ -301,7 +301,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         /// <returns>the parsed element as a Line object, or null if the element is not supported</returns>
         private List<InternalLine> ParseSingleLineSVGElement(string[] currentElement)
         {
-            List<Point> points= null;
+            List<Point> points = null;
             List<InternalLine> element = null;
             switch (currentElement[0])
             {
@@ -350,7 +350,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             double h = 0;
             double rx = 0;
             double ry = 0;
-            for(int j= 0; j < currentElement.Length; j++)
+            for (int j = 0; j < currentElement.Length; j++)
             {
                 if (currentElement[j].StartsWith("x="))
                 {
@@ -540,9 +540,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 }
             }
             List<Point> polygon = new List<Point>();
-            for (int k = 0; k < points.Length - 1; k+=2)
+            for (int k = 0; k < points.Length - 1; k += 2)
             {
-                polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k+1], CultureInfo.InvariantCulture)));
+                polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[k], CultureInfo.InvariantCulture), Convert.ToDouble(points[k + 1], CultureInfo.InvariantCulture)));
             }
             polygon.Add(ScaleAndCreatePoint(Convert.ToDouble(points[0], CultureInfo.InvariantCulture), Convert.ToDouble(points[1], CultureInfo.InvariantCulture))); //close polygon
             return polygon;
@@ -573,12 +573,12 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             NormalizePathDeclaration(pathElements); //expand path data to always explicitly have the command descriptor in front of the appropriate number of arguments and to seperate command descriptors, coordinates and other tokens always into seperate list elements (equivalent to seperation with spaces in the input file, but svg allows also for comma as a seperator, and for omitting seperators where possible without losing information (refer to svg grammer) to reduce file size
             List<InternalLine> element = new List<InternalLine>();
             List<Point> currentLine = new List<Point>();
-            double lastBezierControlPointX= 0;
-            double lastBezierControlPointY= 0;
+            double lastBezierControlPointX = 0;
+            double lastBezierControlPointY = 0;
             double lastPositionX;
             double lastPositionY;
-            double initialPositionX= -1;
-            double initialPositionY= -1;
+            double initialPositionX = -1;
+            double initialPositionY = -1;
             bool newSubpath = true;
             Tuple<List<Point>, double, double> valuesArc; //list of points, new values for: lastPositionX, lastPositionY
             Tuple<List<Point>, double, double, double, double> valuesBezierCurve; //list of points, new values for: lastPositionX, lastPositionY, lastBezierControlPointX, lastBezierControlPointY
@@ -588,7 +588,8 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             lastPositionX = valuesSinglePoint.Item2;
             lastPositionY = valuesSinglePoint.Item3;
             String currentToken;
-            while (!(pathElements.Count == 0)){
+            while (!(pathElements.Count == 0))
+            {
                 if (newSubpath)
                 {
                     initialPositionX = lastPositionX; //update buffers for coordinates of first point of active subpath
@@ -678,7 +679,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                 }
                 else if (currentToken.Equals("c"))
                 {
-                    valuesBezierCurve = Parse_C(pathElements, lastPositionX, lastPositionY);
+                    valuesBezierCurve = Parse_c(pathElements, lastPositionX, lastPositionY);
                     currentLine.AddRange(valuesBezierCurve.Item1); //add point to new line
                     lastPositionX = valuesBezierCurve.Item2; //save last point coordinates
                     lastPositionY = valuesBezierCurve.Item3; //save last point coordinates
@@ -773,7 +774,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
         {
             Char lastCommand = 'M';
             int argumentCounter = 0;
-            for( int j= 0; j < pathElements.Count; j++)
+            for (int j = 0; j < pathElements.Count; j++)
             {
                 String currentElement = pathElements.ElementAt(j);
                 if (currentElement.Length != 1)
@@ -788,8 +789,9 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                     }
                     else if ((currentElement.First() >= '0' && currentElement.First() <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e') //seperate a single coordinate / number
                     {
-                        bool repeatCommandDescriptor = false; 
-                        switch (lastCommand){ //check for reaching of next command with omitted command descriptor
+                        bool repeatCommandDescriptor = false;
+                        switch (lastCommand)
+                        { //check for reaching of next command with omitted command descriptor
                             case 'M':
                                 if (argumentCounter >= 2) repeatCommandDescriptor = true;
                                 break;
@@ -849,7 +851,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         {
                             pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
                             j++; //skip command descriptor (was put into active position in the list
-                            argumentCounter = 0; //reset argument counter
+                            argumentCounter = -1; //reset argument counter
                         }
                         bool decimalPointEncountered = false;
                         for (int k = 1; k < currentElement.Length; k++)
@@ -858,15 +860,15 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                             {
                                 decimalPointEncountered = true;
                             }
-                            else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.First() == '-' || currentElement.First() == '+' || currentElement.First() != 'e'))
+                            else if (!((currentElement.ElementAt(k) >= '0' && currentElement.ElementAt(k) <= '9') || currentElement.ElementAt(k) == 'e'))
                             {
                                 pathElements.RemoveAt(j);
-                                pathElements.Insert(j, currentElement.Substring(0, k - 1)); //insert number as seperate element
+                                pathElements.Insert(j, currentElement.Substring(0, k)); //insert number as seperate element
                                 pathElements.Insert(j + 1, currentElement.Substring(k)); //insert rest of String at next position so it will be processed again
                                 break;
                             }
                         }
-                        argumentCounter++; 
+                        argumentCounter++;
                     }
                     else //parse non-space seperators and skip other unsupported characters (the only other valid ones per svg standard would be weird tokens looking like format descriptors (e.g. '#xC'), these are unsopported and will likely cause an error or other inconsitencies during parsing)
                     {
@@ -888,7 +890,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         lastCommand = currentElement.First();
                         argumentCounter = 0;
                     }
-                    else if(!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
+                    else if (!(currentElement.First() >= '0' && currentElement.First() <= '9')) //not a number
                     {
                         pathElements.RemoveAt(j); //remove element
                         j--; //decrement index pointer so next element will not be skipped (indices of all folowing elements just decreased by 1)
@@ -957,7 +959,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
                         {
                             pathElements.Insert(j, lastCommand + ""); //repeat command descriptor
                             j++; //skip command descriptor (was put into active position in the list
-                            argumentCounter = 0; //reset argument counter
+                            argumentCounter = -1; //reset argument counter
                         }
                         argumentCounter++;
                     }
@@ -1415,14 +1417,14 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             {
                 xPlusFlag = true; //left point lies right of line
             }
-            if(sweepFlag != largeArcFlag) //need "right" center point, not "left" one (refer to svg specification, sweepFlag means going around the circle in "clockwise" direction, largeArcFlag means tracing the larger of the two possible arcs in the selected direction) 
+            if (sweepFlag != largeArcFlag) //need "right" center point, not "left" one (refer to svg specification, sweepFlag means going around the circle in "clockwise" direction, largeArcFlag means tracing the larger of the two possible arcs in the selected direction) 
             {
                 xPlusFlag = !xPlusFlag;
                 yPlusFlag = !yPlusFlag;
             }
             double xC; // coordinates of center point of circle
             double yC;
-            if(xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
+            if (xPlusFlag) xC = x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q); //x3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((y1 - y2) / q);
             else xC = x3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionYRelative) / q);
             if (yPlusFlag) yC = y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q); //y3 + Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((x2-x1) / q);
             else yC = y3 - Math.Sqrt(radsq - ((q / 2) * (q / 2))) * ((nextPositionXRelative) / q);
@@ -1430,7 +1432,7 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             for (int j = 0; j < values.Item1.Length; j++)
             {
                 values.Item1[j] = values.Item1[j] + xC; //correct center point coordinate bias
-                values.Item2[j] = values.Item2[j] + yC; 
+                values.Item2[j] = values.Item2[j] + yC;
             }
             return values;
         }
@@ -1458,13 +1460,13 @@ public Tuple<int, int, List<InternalLine>> ParseISADInputFile(String fileName)
             }
             else // get smaller angleDifference
             {
-                if(angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was larger angleDifference
+                if (angleDifference > Math.PI) angleDifference = ((double)2 * Math.PI) - angleDifference;  // was larger angleDifference
             }
-            int numberOfPoints = (int) Math.Ceiling(angleDifference / angle);  //compute number of points to sample
+            int numberOfPoints = (int)Math.Ceiling(angleDifference / angle);  //compute number of points to sample
             double[] xValues = new double[numberOfPoints];
             double[] yValues = new double[numberOfPoints];
             double phiCurrent = phiStart;
-            for (int j = 0; j < numberOfPoints-1; j++) //compute intermediate points
+            for (int j = 0; j < numberOfPoints - 1; j++) //compute intermediate points
             {
                 if (clockwise) phiCurrent -= angle; //get new angle
                 else phiCurrent += angle;

+ 39 - 0
SketchAssistant/SketchAssistantWPF/Frame.cs

@@ -0,0 +1,39 @@
+namespace OptiTrack
+{
+    public class Frame
+    {
+        public Trackable[] Trackables;
+        public Marker[] Markers;
+
+        public Frame(int markerCount, int trackableCount)
+        {
+            Trackables = new Trackable[trackableCount];
+            Markers = new Marker[markerCount];
+        }
+    }
+
+    public class Trackable
+    {
+        public int Id;
+        public float X, Y, Z, Qx, Qy, Qz, Qw, Pitch, Roll, Yaw;
+        public bool IsTracked;
+
+        internal bool IsAlmostSameCoordinates(Trackable t)
+        {
+            return X == t.X && Y == t.Y && Z == t.Z;
+        }
+    }
+
+    public class Marker
+    {
+        public float X, Y, Z;
+        public Trackable BoundToTrackable;
+
+        public Marker(float x, float y, float z)
+        {
+            X = x;
+            Y = y;
+            Z = z;
+        }
+    }
+}

+ 149 - 0
SketchAssistant/SketchAssistantWPF/GeometryCalculator.cs

@@ -12,6 +12,155 @@ namespace SketchAssistantWPF
     /// </summary>
     public static class GeometryCalculator
     {
+        /// <summary>
+        /// Calculate the approximate similarity of two lines. 
+        /// Using three weighted parameters to calculate a value between 0 and 1 to indicate the similarity of the lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>The similarity of the two lines.</returns>
+        public static double CalculateSimilarity(InternalLine line0, InternalLine line1)
+        {
+            double CosSim = Math.Abs(CalculateAverageCosineSimilarity(line0, line1));
+            double LenSim = CalculateLengthSimilarity(line0, line1);
+            double AvDist = CalculateAverageDistance(line0, line1);
+            double DistSim = (50 - AvDist) / 50;
+            if (DistSim < 0) DistSim = 0;
+            if (CosSim < 0.5 || Double.IsNaN(CosSim)) CosSim = 0;
+            double output = (2 * CosSim + LenSim + DistSim) / 4;
+            System.Diagnostics.Debug.WriteLine("Results: CosSim: {0}, LenSim: {1}, AvDist {2}, DistSim: {3}. Total: {4}",
+                CosSim, LenSim, AvDist, DistSim, output);
+            return output;
+        }
+
+        /// <summary>
+        /// The cosine similarity of two vectors.
+        /// </summary>
+        /// <param name="v0">The first vector</param>
+        /// <param name="v1">The second vector</param>
+        /// <returns>The cosine similarity</returns>
+        private static double CosineSimilarity(Vector v0, Vector v1)
+        {
+            return (v0.X * v1.X + v0.Y * v1.Y) / (Math.Sqrt(v0.X * v0.X + v0.Y * v0.Y) * Math.Sqrt(v1.X * v1.X + v1.Y * v1.Y));
+        }
+
+        /// <summary>
+        /// An approximate calculation of the average cosine similarity 
+        /// of two lines, achieved by calculating the cosine similarity of subvectors.
+        /// </summary>
+        /// <param name="line0">The first line</param>
+        /// <param name="line1">The second line</param>
+        /// <returns>The approximate average cosine similarity of all subvectors</returns>
+        public static double CalculateAverageCosineSimilarity(InternalLine line0, InternalLine line1)
+        {
+            //check if one of the lines is a point, or both lines are points
+            if ((line0.isPoint && !line1.isPoint) || (line1.isPoint && !line0.isPoint)) return 0;
+            else if (line0.isPoint && line1.isPoint) return 1;
+
+            List<Point> points0 = line0.GetPoints();
+            List<Point> points1 = line1.GetPoints();
+
+            if (points0.Count == points1.Count)
+            {
+                //If the two lists have an equal amount of subvectors, 
+                //compare the nth subvectors from each list and average the value.
+                double sum = 0; int i = 0;
+                List<Point> shortL = points0; List<Point> longL = points1;
+                for (; i < shortL.Count - 1; i++)
+                {
+                    if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
+                    Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                    Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
+                    sum += CosineSimilarity(v0, v1);
+                }
+                return sum / i;
+            }
+            else
+            {
+                //Determine if the longer list is of similar length or contains significatly more items
+                List<Point> shortL = points0; List<Point> longL = points0;
+                if (points0.Count < points1.Count) { longL = points1; }
+                if (points0.Count > points1.Count) { shortL = points1; }
+                double dif = 2;
+                if (shortL.Count != 1)
+                {
+                    dif = (longL.Count - 1) / (shortL.Count - 1);
+                }
+                
+                if (dif > 1)
+                {
+                    //The longer list is significantly longer
+                    //Each element in the shorter list is compared to multiple 
+                    // elements in the longer list to make up the difference
+                    double sum = 0; int adds = 0;
+
+                    for (int i = 0; i < shortL.Count - 1; i++)
+                    {
+                        if (i + 1 == shortL.Count) break;
+                        for (int j = 0; j <= dif; j++)
+                        {
+                            var k = i + j;
+                            if (k + 1 == longL.Count) break;
+                            Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                            Vector v1 = new Vector(longL[k + 1].X - longL[k].X, longL[k + 1].Y - longL[k].Y);
+                            sum += CosineSimilarity(v0, v1); adds++;
+                        }
+                    }
+                    return sum / adds;
+                }
+                else
+                {
+                    //The longer list is almost the same length as the shorter list
+                    //The remaining items are simply skipped
+                    double sum = 0; int i = 0;
+                    for (; i < shortL.Count - 1; i++)
+                    {
+                        if (i + 1 == shortL.Count || i + 1 == longL.Count) break;
+                        Vector v0 = new Vector(shortL[i + 1].X - shortL[i].X, shortL[i + 1].Y - shortL[i].Y);
+                        Vector v1 = new Vector(longL[i + 1].X - longL[i].X, longL[i + 1].Y - longL[i].Y);
+                        sum += CosineSimilarity(v0, v1);
+                    }
+                    return sum / i;
+                }
+            }
+        }
+
+        /// <summary>
+        /// Calculate the similarity in length of two Lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>How similar the lines are in length.</returns>
+        private static double CalculateLengthSimilarity(InternalLine line0, InternalLine line1)
+        {
+            double len0 = line0.GetLength(); double len1 = line1.GetLength();
+            var dif = Math.Abs(len1 - len0);
+            if (dif == 0) return 1;
+            double shorter;
+            if (len1 > len0) shorter = len0;
+            else shorter = len1;
+            if (dif >= shorter) return 0;
+            return (shorter - dif) / shorter;
+        }
+
+        /// <summary>
+        /// Calculate the average distance between the ends of two lines.
+        /// </summary>
+        /// <param name="line0">The first line.</param>
+        /// <param name="line1">The second line.</param>
+        /// <returns>The shortest average distance between the ends of the lines.</returns>
+        private static double CalculateAverageDistance(InternalLine line0, InternalLine line1)
+        {
+            List<Point> points0 = line0.GetPoints();
+            List<Point> points1 = line1.GetPoints();
+            double distfirstfirst = Math.Sqrt(Math.Pow((points0[0].X - points1[0].X), 2) + Math.Pow((points0[0].Y - points1[0].Y), 2));
+            double distlastlast = Math.Sqrt(Math.Pow((points0.Last().X - points1.Last().X), 2) + Math.Pow((points0.Last().Y - points1.Last().Y), 2));
+            double distfirstlast = Math.Sqrt(Math.Pow((points0[0].X - points1.Last().X), 2) + Math.Pow((points0[0].Y - points1.Last().Y), 2));
+            double distlastfirst = Math.Sqrt(Math.Pow((points0.Last().X - points1[0].X), 2) + Math.Pow((points0.Last().Y - points1[0].Y), 2));
+            if ((distfirstfirst + distlastlast) / 2 < (distfirstlast + distlastfirst) / 2) return (distfirstfirst + distlastlast) / 2;
+            else return (distfirstlast + distlastfirst) / 2;
+        }
+
         /// <summary>
         /// A simple algorithm that returns a filled circle with a radius and a center point.
         /// </summary>

+ 39 - 0
SketchAssistant/SketchAssistantWPF/HighPerformanceTimer.cs

@@ -0,0 +1,39 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Runtime.InteropServices;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace OptiTrack
+{
+    public class HighPerformanceTimer
+    {
+        [DllImport("Kernel32.dll")]
+        private static extern bool QueryPerformanceCounter(out long lpPerformanceCount);
+
+        [DllImport("Kernel32.dll")]
+        private static extern bool QueryPerformanceFrequency(out long lpFrequency);
+
+        private long start;
+        private long freq;
+
+        public HighPerformanceTimer()
+        {
+            start = 0;
+            QueryPerformanceFrequency(out freq);
+        }
+
+        public void Start()
+        {
+            QueryPerformanceCounter(out start);
+        }
+
+        public double Stop()
+        {
+            long stop;
+            QueryPerformanceCounter(out stop);
+            return (double)(stop - start) / (double)freq;
+        }
+    }
+}

+ 43 - 14
SketchAssistant/SketchAssistantWPF/InternalLine.cs

@@ -34,6 +34,10 @@ namespace SketchAssistantWPF
         /// The location of the point, if this is a point
         /// </summary>
         public Point point { get; private set; }
+        /// <summary>
+        /// The length of the line
+        /// </summary>
+        private double length = -1;
 
         /// <summary>
         /// The constructor for lines which are only temporary.
@@ -62,14 +66,18 @@ namespace SketchAssistantWPF
             isTemporary = false;
         }
 
-        public Point GetStartPoint()
-        {
-            return linePoints.First();
-        }
-
-        public Point GetEndPoint()
+        /// <summary>
+        /// A function to make temporary lines non-temporary.
+        /// </summary>
+        /// <param name="id">The id of the line.</param>
+        public void MakePermanent(int id)
         {
-            return linePoints.Last();
+            if (isTemporary)
+            {
+                identifier = id;
+                CleanPoints();
+                isTemporary = false;
+            }
         }
 
         public List<Point> GetPoints()
@@ -87,6 +95,26 @@ namespace SketchAssistantWPF
             return pointColl;
         }
 
+
+        /// <summary>
+        /// Get the length of the line.
+        /// </summary>
+        /// <returns>The length of the line.</returns>
+        public double GetLength()
+        {
+            if (length < 0)
+            {
+                length = 0;
+                for (int i = 0; i < linePoints.Count - 1; i++)
+                {
+                    var a = linePoints[i]; var b = linePoints[i + 1];
+
+                    length += Math.Sqrt(Math.Pow((a.X - b.X), 2) + Math.Pow((a.Y - b.Y), 2));
+                }
+            }
+            return length;
+        }
+
         /// <summary>
         /// A function that will take two matrixes and populate them with the line data of this line object
         /// </summary>
@@ -101,12 +129,12 @@ namespace SketchAssistantWPF
                     if (currPoint.X >= 0 && currPoint.Y >= 0 &&
                         currPoint.X < boolMatrix.GetLength(0) && currPoint.Y < boolMatrix.GetLength(1))
                     {
-                        boolMatrix[(int) currPoint.X, (int) currPoint.Y] = true;
-                        if (listMatrix[(int) currPoint.X, (int) currPoint.Y] == null)
+                        boolMatrix[(int)currPoint.X, (int)currPoint.Y] = true;
+                        if (listMatrix[(int)currPoint.X, (int)currPoint.Y] == null)
                         {
-                            listMatrix[(int) currPoint.X, (int) currPoint.Y] = new HashSet<int>();
+                            listMatrix[(int)currPoint.X, (int)currPoint.Y] = new HashSet<int>();
                         }
-                        listMatrix[(int) currPoint.X, (int) currPoint.Y].Add(identifier);
+                        listMatrix[(int)currPoint.X, (int)currPoint.Y].Add(identifier);
                     }
                 }
             }
@@ -121,16 +149,17 @@ namespace SketchAssistantWPF
             {
                 //check if its a point
                 var localIsPoint = linePoints.All(o => o.X == linePoints.First().X && o.Y == linePoints.First().Y);
-                if (!localIsPoint) {
+                if (!localIsPoint)
+                {
                     List<Point> newList = new List<Point>();
                     List<Point> tempList = new List<Point>();
                     //Since Point is non-nullable, we must ensure the nullPoints, 
                     //which we remove can not possibly be points of the original given line.
-                    int nullValue = (int) linePoints[0].X + 1;
+                    int nullValue = (int)linePoints[0].X + 1;
                     //Fill the gaps between points
                     for (int i = 0; i < linePoints.Count - 1; i++)
                     {
-                        nullValue += (int) linePoints[i + 1].X;
+                        nullValue += (int)linePoints[i + 1].X;
                         List<Point> partialList = GeometryCalculator.BresenhamLineAlgorithm(linePoints[i], linePoints[i + 1]);
                         tempList.AddRange(partialList);
                     }

+ 97 - 6
SketchAssistant/SketchAssistantWPF/LocalArmbandInterface.cs

@@ -7,22 +7,113 @@ using System.Runtime.InteropServices;
 
 namespace SketchAssistantWPF
 {
-    class LocalArmbandInterface
+    /// <summary>
+    /// interface providing access to the vibrotactile armband
+    /// accessing the BodyActuator.dll C library via the StaticLibMotors.dll c++ library
+    /// </summary>
+    public static class LocalArmbandInterface
     {
+        /// <summary>
+        /// constant to set the vibration mode:
+        /// 0 -> no vibration
+        /// 1 -> intensity 0.33
+        /// 2 -> intensity 0.66
+        /// 3 -> intensity 1.0
+        /// </summary>
+        static readonly int VIBRATION_MODE = 2;
 
+
+        /// <summary>
+        /// initializes the armband (and binds the C dll)
+        /// must be called before calling any other of the methods of this class
+        /// explicitly allocates memory and therefore must only be called once and must be followed by a call to DestroyArmband eventually
+        /// </summary>
+        /// <returns>an integer purely for debugging purposes, -1 means no unexpected behaviour occured and the initialization was successful</returns>
         [DllImport (@"../Debug/StaticLibMotors.dll", EntryPoint = "?setupArmband@ArmbandInterface@@QAAHXZ",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern int setupArmband();
+        public static extern int SetupArmband();
+
+        /// <summary>
+        /// destroys the armband instance created by SetupArmband (thus freeing its allocated memory)
+        /// </summary>
+        /// <returns></returns>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?destroyArmband@ArmbandInterface@@QAAHXZ",
+     CallingConvention = CallingConvention.Cdecl)]
+        public static extern int DestroyArmband();
 
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at the specified intensity (until it is stopped)
+        /// </summary>
+        /// <param name="motorNumber">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
         [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?startVibrate@ArmbandInterface@@QAAXHM@Z",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern void startVibrate(int motorNumber, float intensity);
+        public static extern void StartVibrate(int motorNumber, double intensity);
+
+        /// <summary>
+        /// stop actuation of the specified tactor (motor)
+        /// </summary>
+        /// <param name="motorNumber">integer from 0 to 7 specifying the number of the tactor to stop</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?stopVibrate@ArmbandInterface@@QAAXH@Z",                       
+     CallingConvention = CallingConvention.Cdecl)]
+        public static extern void StopVibration(int motorNumber);
+
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at  0.33 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate33@ArmbandInterface@@QAAXHNH@Z",
+     CallingConvention = CallingConvention.Cdecl)]
+        private static extern void Actuate33(int tactor, double intensity, int duration);
+
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at intensity 0.66 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate66@ArmbandInterface@@QAAXHNH@Z",
+     CallingConvention = CallingConvention.Cdecl)]
+        private static extern void Actuate66(int tactor, double intensity, int duration);
 
-        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?stopVibrate@ArmbandInterface@@QAAXH@Z",
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at intensity 1.0 for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        [DllImport(@"../Debug/StaticLibMotors.dll", EntryPoint = "?actuate100@ArmbandInterface@@QAAXHNH@Z",
      CallingConvention = CallingConvention.Cdecl)]
-        public static extern void stopVibration(int motorNumber);
+        private static extern void Actuate100(int tactor, double intensity, int duration);
 
-        //public void Vibrate()
+        /// <summary>
+        /// starts actuation of the specified tactor (motor) at the specified intensity (specified in VIBRATION_MODE) for a specified amount of time
+        /// </summary>
+        /// <param name="tactor">integer from 0 to 7 specifying the number of the tactor to actuate</param>
+        /// <param name="intensity">intensity, ranging from 0.0 to 1.0 by default</param>
+        /// <param name="duration">number of millisecons to actuate the tactor for</param>
+        public static void Actuate(int tactor, double intensity, int duration)
+        {
+            switch (VIBRATION_MODE)
+            {
+                case 0:
+                    break;
+                case 1:
+                    Actuate33(tactor, intensity, duration);
+                    break;
+                case 2:
+                    Actuate66(tactor, intensity, duration);
+                    break;
+                case 3:
+                    Actuate100(tactor, intensity, duration);
+                    break;
+                default:
+                    Console.WriteLine("Error: invalid value for VIBRATION_MODE constant");
+                    break;
+            }
+        }
 
     }
 }

+ 404 - 103
SketchAssistant/SketchAssistantWPF/MVP_Model.cs

@@ -7,6 +7,10 @@ using System.Threading.Tasks;
 using System.Windows.Controls;
 using System.Windows.Media;
 using System.Windows.Media.Imaging;
+using OptiTrack;
+using System.Runtime.InteropServices;
+using System.IO;
+using System.IO.Ports;
 
 namespace SketchAssistantWPF
 {
@@ -21,27 +25,40 @@ namespace SketchAssistantWPF
         /// </summary>
         ActionHistory historyOfActions;
         /// <summary>
-        /// The assistant responsible for the redraw mode
+        /// The connector class used to get frames from the Optitrack system.
         /// </summary>
-        //RedrawAssistant redrawAss;
+        OptiTrackConnector connector;
 
 
+        /// <summary>
+        /// intensity of the tectile feedback occuring once every passed centimeter (ranging from 0.0 to 1.0)
+        /// </summary>
+        static readonly double TACTILE_SURFACE_FEEDBACK_INTENSITY = 1.0;
+        /// <summary>
+        /// duration of the tectile feedback occuring once every passed centimeter (in milliseconds)
+        /// </summary>
+        static readonly int TACTILE_SURFACE_FEEDBACK_DURATION = 15;
+
         /***********************/
         /*** CLASS VARIABLES ***/
         /***********************/
 
+        /// <summary>
+        /// this is a variable used for detecting whether the tracker is in the warning zone (0 +- variable), no drawing zone (0 +- 2 * variable) or normal drawing zone
+        /// </summary>
+        readonly double WARNING_ZONE_BOUNDARY = 0.10; //10cm
         /// <summary>
         /// If the program is in drawing mode.
         /// </summary>
-        bool inDrawingMode;
+        public bool inDrawingMode { get; private set; }
         /// <summary>
-        /// Size of deletion area
+        /// if the program is using OptiTrack
         /// </summary>
-        int deletionRadius = 5;
+        public bool optiTrackInUse { get; private set; }
         /// <summary>
-        /// Size of areas marking endpoints of lines in the redraw mode.
+        /// Size of deletion area
         /// </summary>
-        int markerRadius = 10;
+        int deletionRadius = 5;
         /// <summary>
         /// The Position of the Cursor in the right picture box
         /// </summary>
@@ -55,6 +72,18 @@ namespace SketchAssistantWPF
         /// </summary>
         Queue<Point> cursorPositions = new Queue<Point>();
         /// <summary>
+        /// The Position of the Cursor of opti track
+        /// </summary>
+        Point currentOptiCursorPosition;
+        /// <summary>
+        /// The Previous Cursor Position of opti track
+        /// </summary>
+        Point previousOptiCursorPosition;
+        /// <summary>
+        /// Queue for the cursorPositions of opti track
+        /// </summary>
+        Queue<Point> optiCursorPositions = new Queue<Point>();
+        /// <summary>
         /// Lookup Matrix for checking postions of lines in the image
         /// </summary>
         bool[,] isFilledMatrix;
@@ -63,10 +92,6 @@ namespace SketchAssistantWPF
         /// </summary>
         HashSet<int>[,] linesMatrix;
         /// <summary>
-        /// List of items which will be overlayed over the right canvas.
-        /// </summary>
-        List<Tuple<bool, HashSet<Point>>> overlayItems;
-        /// <summary>
         /// Width of the LeftImageBox.
         /// </summary>
         public int leftImageBoxWidth;
@@ -82,48 +107,113 @@ namespace SketchAssistantWPF
         /// Height of the RightImageBox.
         /// </summary>
         public int rightImageBoxHeight;
-
-        public ImageDimension leftImageSize { get; private set; }
-
+        /// <summary>
+        /// The size of the right canvas.
+        /// </summary>
         public ImageDimension rightImageSize { get; private set; }
         /// <summary>
         /// Indicates whether or not the canvas on the right side is active.
         /// </summary>
-        public bool canvasActive {get; set;}
+        public bool canvasActive { get; set; }
         /// <summary>
         /// Indicates if there is a graphic loaded in the left canvas.
         /// </summary>
         public bool graphicLoaded { get; set; }
         /// <summary>
+        /// Whether or not an optitrack system is avaiable.
+        /// </summary>
+        public bool optitrackAvailable { get; private set; }
+        /// <summary>
+        /// x coordinate in real world. one unit is one meter. If standing in front of video wall facing it, moving left results in incrementation of x.
+        /// </summary>
+        public float optiTrackX;
+        /// <summary>
+        /// y coordinate in real world. one unit is one meter. If standing in front of video wall, moving up results in incrementation of y.
+        /// </summary>
+        public float optiTrackY;
+        /// <summary>
+        /// z coordinate in real world. one unit is one meter. If standing in front of video wall, moving back results in incrementation of y.
+        /// </summary>
+        public float optiTrackZ;
+        /// <summary>
+        /// keeps track of whether last tick the trackable was inside drawing zone or not.
+        /// </summary>
+        private bool optiTrackInsideDrawingZone = false;
+        /// <summary>
+        /// object of class wristband used for controlling the vibrotactile wristband
+        /// </summary>
+        private Wristband wristband;
+        /// <summary>
+        /// Is set to true when the trackable has passed to the backside of the drawing surface, 
+        /// invalidating all inputs on its way back.
+        /// </summary>
+        bool OptiMovingBack = false;
+        /// <summary>
+        /// The Layer in which the optitrack system was. 0 is drawing layer, -1 is in front, 1 is behind
+        /// </summary>
+        int OptiLayer = 0;
+        /// <summary>
+        /// The path traveled since the last tick
+        /// </summary>
+        double PathTraveled = 0;
+        /// <summary>
         /// Whether or not the mouse is pressed.
         /// </summary>
         private bool mouseDown;
-
-        Image rightImageWithoutOverlay;
-
+        /// <summary>
+        /// A List of lines in the left canvas.
+        /// </summary>
         List<InternalLine> leftLineList;
-        
+        /// <summary>
+        /// A list of lines in the right canvas along with a boolean indicating if they should be drawn.
+        /// </summary>
         List<Tuple<bool, InternalLine>> rightLineList;
-
+        /// <summary>
+        /// The line currently being drawin with optitrack.
+        /// </summary>
         List<Point> currentLine = new List<Point>();
-
-
+        /// <summary>
+        /// If the COM5 port is available.
+        /// </summary>
+        bool comFiveAvailable = false;
 
         public MVP_Model(MVP_Presenter presenter)
         {
             //TODO remove
-            Console.WriteLine("trying to initialize Armband...");
-            int tmp= LocalArmbandInterface.setupArmband();
-            Console.WriteLine("Armband initialization terminated, exit code: " + tmp);
+            var portName = "COM5";
+
+            var isValid = SerialPort.GetPortNames().Any(x => string.Compare(x, portName, true) == 0);
+            if (isValid)
+            {
+                Console.WriteLine("trying to initialize Armband...");
+                int tmp = LocalArmbandInterface.SetupArmband();
+                Console.WriteLine("Armband initialization terminated, exit code: " + tmp);
+                comFiveAvailable = true;
+            }
+            else
+            {
+                Console.WriteLine("COM5 device not found");
+            }
+
             programPresenter = presenter;
             historyOfActions = new ActionHistory();
-            //redrawAss = new RedrawAssistant();
-            //overlayItems = new List<Tuple<bool, HashSet<Point>>>();
             rightLineList = new List<Tuple<bool, InternalLine>>();
             canvasActive = false;
             UpdateUI();
             rightImageSize = new ImageDimension(0, 0);
-            leftImageSize = new ImageDimension(0, 0);
+            connector = new OptiTrackConnector();
+            wristband = new Wristband();
+
+            //Set up Optitrack
+            optitrackAvailable = false;
+            if (File.Exists(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+            {
+                if (connector.Init(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
+                {
+                    optitrackAvailable = true;
+                    connector.StartTracking(GetOptiTrackPosition);
+                }
+            }
         }
 
         /**************************/
@@ -131,6 +221,31 @@ namespace SketchAssistantWPF
         /**************************/
 
 
+        /// <summary>
+        /// Check if the Optitrack trackable is in the drawing plane.
+        /// </summary>
+        /// <param name="optiTrackZ">The real world z coordinates of the trackable.</param>
+        /// <returns>If the trackable is in front of the drawing plane</returns>
+        private bool CheckInsideDrawingZone(float optiTrackZ)
+        {
+            if (Math.Abs(optiTrackZ) > WARNING_ZONE_BOUNDARY * 2) return false;
+            return true;
+        }
+
+        /// <summary>
+        /// Function that is called by the OptitrackController to pass frames to the model.
+        /// </summary>
+        /// <param name="frame">An Optitrack Frame</param>
+        void GetOptiTrackPosition(OptiTrack.Frame frame)
+        {
+            if (frame.Trackables.Length >= 1)
+            {
+                optiTrackX = frame.Trackables[0].X;
+                optiTrackY = frame.Trackables[0].Y;
+                optiTrackZ = frame.Trackables[0].Z;
+            }
+        }
+
         /// <summary>
         /// Change the status of whether or not the lines are shown.
         /// </summary>
@@ -147,6 +262,24 @@ namespace SketchAssistantWPF
             }
         }
 
+
+        /// <summary>
+        /// Check if enough distance has been travelled to warrant a vibration.
+        /// </summary>
+        private void CheckPathTraveled()
+        {
+            var a = Math.Abs(previousOptiCursorPosition.X - currentOptiCursorPosition.X);
+            var b = Math.Abs(previousOptiCursorPosition.Y - currentOptiCursorPosition.Y);
+            PathTraveled += Math.Sqrt(Math.Pow(a,2) + Math.Pow(b,2));
+            //Set the Interval of vibrations here
+            if(PathTraveled > 15)
+            {
+                PathTraveled = 0;
+                if(comFiveAvailable)
+                    LocalArmbandInterface.Actuate(0, TACTILE_SURFACE_FEEDBACK_INTENSITY, TACTILE_SURFACE_FEEDBACK_DURATION);
+            }
+        }
+
         /// <summary>
         /// A function that populates the matrixes needed for deletion detection with line data.
         /// </summary>
@@ -171,7 +304,7 @@ namespace SketchAssistantWPF
         /// </summary>
         private void UpdateUI()
         {
-            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded);
+            programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded, optitrackAvailable, optiTrackInUse);
         }
 
         /// <summary>
@@ -198,6 +331,60 @@ namespace SketchAssistantWPF
             return returnSet;
         }
 
+        /// <summary>
+        /// Converts given point to device-independent pixel.
+        /// </summary>
+        /// <param name="p">real world coordinate</param>
+        /// <returns>The given Point converted to device-independent pixel </returns>
+        private Point ConvertTo96thsOfInch(Point p)
+        {
+            //The position of the optitrack coordinate system
+            // and sizes of the optitrack tracking area relative to the canvas.
+            double OPTITRACK_X_OFFSET = -0.4854;
+            double OPTITRACK_Y_OFFSET = 0.9;
+            double OPTITRACK_CANVAS_HEIGHT = 1.2;
+            double OPTITRACK_X_SCALE = 0.21 * (((1.8316/*size of canvas*/ / 0.0254) * 96) / (1.8316));
+            double OPTITRACK_Y_SCALE = 0.205 * (((1.2 / 0.0254) * 96) / (1.2));
+            //The coordinates on the display
+            double xCoordinate = (p.X - OPTITRACK_X_OFFSET) * OPTITRACK_X_SCALE;
+            double yCoordinate = (OPTITRACK_CANVAS_HEIGHT - (p.Y - OPTITRACK_Y_OFFSET)) * OPTITRACK_Y_SCALE;
+            return new Point(xCoordinate, yCoordinate);
+        }
+
+        /// <summary>
+        /// Updates the Optitrack coordiantes, aswell as the marker for optitrack.
+        /// </summary>
+        /// <param name="p">The new cursor position</param>
+        private void SetCurrentFingerPosition(Point p)
+        {
+            Point correctedPoint = ConvertTo96thsOfInch(p);
+
+
+            if (optiTrackZ < -2.2 * WARNING_ZONE_BOUNDARY && OptiLayer > -1)
+            {
+                OptiLayer = -1; OptiMovingBack = false;
+                programPresenter.SetOverlayColor("optipoint", Brushes.Yellow);
+            }
+            else if (optiTrackZ > 2 * WARNING_ZONE_BOUNDARY && OptiLayer < 1)
+            {
+                programPresenter.SetOverlayColor("optipoint", Brushes.Red);
+                OptiLayer = 1;
+            }
+            else if(optiTrackZ <= 2 * WARNING_ZONE_BOUNDARY && optiTrackZ >= -2.2 * WARNING_ZONE_BOUNDARY){
+                if(OptiLayer > 0)
+                    OptiMovingBack = true;
+                programPresenter.SetOverlayColor("optipoint", Brushes.Green);
+                OptiLayer = 0;
+            }
+
+            currentOptiCursorPosition = correctedPoint;
+            programPresenter.MoveOptiPoint(currentOptiCursorPosition);
+
+            if (optiCursorPositions.Count > 0) { previousOptiCursorPosition = optiCursorPositions.Dequeue(); }
+            else { previousOptiCursorPosition = currentOptiCursorPosition; }
+            optiCursorPositions.Enqueue(currentOptiCursorPosition);
+        }
+
         /********************************************/
         /*** FUNCTIONS TO INTERACT WITH PRESENTER ***/
         /********************************************/
@@ -205,13 +392,10 @@ namespace SketchAssistantWPF
         /// <summary>
         /// A function to update the dimensions of the left and right canvas when the window is resized.
         /// </summary>
-        /// <param name="LeftCanvas">The size of the left canvas.</param>
         /// <param name="RightCanvas">The size of the right canvas.</param>
-        public void ResizeEvent(ImageDimension LeftCanvas, ImageDimension RightCanvas)
+        public void ResizeEvent(ImageDimension RightCanvas)
         {
-            if(LeftCanvas.Height >= 0 && LeftCanvas.Width>= 0) { leftImageSize = LeftCanvas; }
-            if(RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
-          
+            if (RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
             RepopulateDeletionMatrixes();
         }
 
@@ -220,6 +404,8 @@ namespace SketchAssistantWPF
         /// </summary>
         public void ResetRightImage()
         {
+            if(currentLine.Count > 0)
+                FinishCurrentLine(true);
             rightLineList.Clear();
             programPresenter.PassLastActionTaken(historyOfActions.Reset());
             programPresenter.ClearRightLines();
@@ -233,30 +419,11 @@ namespace SketchAssistantWPF
         /// <param name="listOfLines">The List of Lines to be displayed in the left image.</param>
         public void SetLeftLineList(int width, int height, List<InternalLine> listOfLines)
         {
-            leftImageSize = new ImageDimension(width, height);
             rightImageSize = new ImageDimension(width, height);
             leftLineList = listOfLines;
             graphicLoaded = true;
             programPresenter.UpdateLeftLines(leftLineList);
-            //programPresenter.ClearRightLines(); //TODO check if right position for this method call
             CanvasActivated();
-            /*
-            var workingCanvas = GetEmptyCanvas(width, height);
-            var workingGraph = Graphics.FromImage(workingCanvas);
-            leftLineList = listOfLines;
-            //redrawAss = new RedrawAssistant(leftLineList);
-            //overlayItems = redrawAss.Initialize(markerRadius);
-            //Lines
-            foreach (InternalLine line in leftLineList)
-            {
-                line.DrawLine(workingGraph);
-            }
-            leftImage = workingCanvas;
-            programPresenter.UpdateLeftImage(leftImage);
-            //Set right image to same size as left image and delete linelist
-            DrawEmptyCanvasRight();
-            rightLineList = new List<Tuple<bool, InternalLine>>();
-            */
         }
 
         /// <summary>
@@ -291,7 +458,6 @@ namespace SketchAssistantWPF
                     default:
                         break;
                 }
-                //TODO: For the person implementing overlay: Add check if overlay needs to be added
                 programPresenter.UpdateRightLines(rightLineList);
             }
             RepopulateDeletionMatrixes();
@@ -322,7 +488,6 @@ namespace SketchAssistantWPF
                     default:
                         break;
                 }
-                //TODO: For the person implementing overlay: Add check if overlay needs to be added
                 programPresenter.UpdateRightLines(rightLineList);
                 RepopulateDeletionMatrixes();
             }
@@ -335,10 +500,33 @@ namespace SketchAssistantWPF
         /// <param name="nowDrawing">The new drawingstate of the program</param>
         public void ChangeState(bool nowDrawing)
         {
+            if(inDrawingMode && !nowDrawing && currentLine.Count > 0 && optiTrackInUse)
+                FinishCurrentLine(true);
             inDrawingMode = nowDrawing;
             UpdateUI();
         }
-        
+
+        /// <summary>
+        /// The function called by the Presenter to set a variable which describes if OptiTrack is in use
+        /// </summary>
+        /// <param name="usingOptiTrack">The status of optitrack button</param>
+        public void SetOptiTrack(bool usingOptiTrack)
+        {
+            optiTrackInUse = usingOptiTrack;
+            if (usingOptiTrack && optiTrackX == 0 && optiTrackY == 0 && optiTrackZ == 0)
+            {
+                programPresenter.PassWarning("Trackable not detected, please check if OptiTrack is activated and Trackable is recognized");
+                optiTrackInUse = false;
+                //Disable optipoint
+                programPresenter.SetOverlayStatus("optipoint", false, currentCursorPosition);
+            }
+            else
+            {
+                //Enable optipoint
+                programPresenter.SetOverlayStatus("optipoint", true, currentCursorPosition);
+            }
+        }
+
         /// <summary>
         /// Updates the current cursor position of the model.
         /// </summary>
@@ -352,24 +540,31 @@ namespace SketchAssistantWPF
         /// <summary>
         /// Start a new Line, when the Mouse is pressed down.
         /// </summary>
-        public void MouseDown()
+        public void StartNewLine()
         {
             mouseDown = true;
-            if (inDrawingMode && mouseDown)
+            if (inDrawingMode)
             {
-                currentLine.Clear();
-                currentLine.Add(currentCursorPosition);
+                if(optiTrackInUse)
+                {
+                    currentLine.Clear();
+                    currentLine.Add(currentOptiCursorPosition);
+                }
+                else if (programPresenter.IsMousePressed())
+                {
+                    currentLine.Clear();
+                    currentLine.Add(currentCursorPosition);
+                }
             }
-            //TODO remove
-            LocalArmbandInterface.startVibrate(0, 1);
         }
 
         /// <summary>
         /// Finish the current Line, when the pressed Mouse is released.
         /// </summary>
         /// <param name="valid">Whether the up event is valid or not</param>
-        public void MouseUp(bool valid)
+        public void FinishCurrentLine(bool valid)
         {
+
             mouseDown = false;
             if (valid)
             {
@@ -382,7 +577,7 @@ namespace SketchAssistantWPF
                     //TODO: For the person implementing overlay: Add check if overlay needs to be added
                     programPresenter.UpdateRightLines(rightLineList);
                     currentLine.Clear();
-                    //programPresenter.UpdateCurrentLine(currentLine);
+                    programPresenter.UpdateCurrentLine(currentLine);
                 }
             }
             else
@@ -390,8 +585,26 @@ namespace SketchAssistantWPF
                 currentLine.Clear();
             }
             UpdateUI();
-            //TODO remove
-            LocalArmbandInterface.stopVibration(0);
+        }
+
+        /// <summary>
+        /// Finish the current Line, when the pressed Mouse is released.
+        /// Overload that is used to pass a list of points to be used when one is available.
+        /// </summary>
+        /// <param name="p">The list of points</param>
+        public void FinishCurrentLine(List<Point> p)
+        {
+            mouseDown = false;
+            if (inDrawingMode && currentLine.Count > 0)
+            {
+                InternalLine newLine = new InternalLine(p, rightLineList.Count);
+                rightLineList.Add(new Tuple<bool, InternalLine>(true, newLine));
+                newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
+                programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
+                programPresenter.UpdateRightLines(rightLineList);
+                currentLine.Clear();
+            }
+            UpdateUI();
         }
 
         /// <summary>
@@ -399,19 +612,83 @@ namespace SketchAssistantWPF
         /// </summary>
         public void Tick()
         {
+            //Show startpoints
+            if (leftLineList != null && leftLineList.Count > 0)
+            {
+                var templist = rightLineList.Where(tup => tup.Item1).ToList();
+                if (templist.Count < leftLineList.Count)
+                {
+                    programPresenter.SetOverlayStatus("startpoint", true, leftLineList[templist.Count].GetPoints()[0]);
+                }
+                else
+                {
+                    programPresenter.SetOverlayStatus("startpoint", false, new Point(50, 50));
+                }
+
+            }
+
             if (cursorPositions.Count > 0) { previousCursorPosition = cursorPositions.Dequeue(); }
             else { previousCursorPosition = currentCursorPosition; }
-            cursorPositions.Enqueue(currentCursorPosition);
-            //Drawing
-            if (inDrawingMode && programPresenter.IsMousePressed())
+
+            if(optitrackAvailable)
+                SetCurrentFingerPosition(new Point(optiTrackX, optiTrackY));
+            
+
+            if (optiTrackInUse && inDrawingMode && !OptiMovingBack) // optitrack is being used
+            {
+                //outside of drawing zone
+                if (!CheckInsideDrawingZone(optiTrackZ))
+                {
+                    //Check if trackable was in drawing zone last tick & program is in drawing mode-> finish line
+                    if (optiTrackInsideDrawingZone && inDrawingMode) 
+                    {
+                        optiTrackInsideDrawingZone = false;
+                        FinishCurrentLine(true);
+                    }
+                }
+                else //Draw with optitrack, when in drawing zone
+                {
+                    //Optitrack wasn't in the drawing zone last tick -> start a new line
+                    if (!optiTrackInsideDrawingZone)
+                    {
+                        optiTrackInsideDrawingZone = true;
+                        StartNewLine();
+                    }
+                    else currentLine.Add(currentOptiCursorPosition);
+                    programPresenter.UpdateCurrentLine(currentLine);
+                    if (optiTrackZ > WARNING_ZONE_BOUNDARY)
+                    {
+                        wristband.PushForward();
+                    }
+                    else if (optiTrackZ < -1 * WARNING_ZONE_BOUNDARY)
+                    {
+                        wristband.PushBackward();
+                    }
+
+                    CheckPathTraveled();
+                }
+            }
+            else if( !optiTrackInUse && inDrawingMode)
             {
-                currentLine.Add(currentCursorPosition);
-                //programPresenter.UpdateCurrentLine(currentLine);
+                //drawing without optitrack
+                cursorPositions.Enqueue(currentCursorPosition);
+                if (inDrawingMode && programPresenter.IsMousePressed())
+                {
+                    currentLine.Add(currentCursorPosition);
+                    //programPresenter.UpdateCurrentLine(currentLine);
+                }
+
             }
-            //Deleting
-            if (!inDrawingMode && programPresenter.IsMousePressed())
+
+            //Deletion mode for optitrack and regular use
+            if (!inDrawingMode)
             {
-                List<Point> uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+                List<Point> uncheckedPoints = new List<Point>();
+                if (programPresenter.IsMousePressed() && !optiTrackInUse) //without optitrack
+                    uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
+                if(optiTrackInUse && CheckInsideDrawingZone(optiTrackZ)  && !OptiMovingBack) //with optitrack
+                    uncheckedPoints = GeometryCalculator.BresenhamLineAlgorithm(previousOptiCursorPosition, currentOptiCursorPosition);
+
                 foreach (Point currPoint in uncheckedPoints)
                 {
                     HashSet<int> linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionRadius);
@@ -423,44 +700,68 @@ namespace SketchAssistantWPF
                             rightLineList[lineID] = new Tuple<bool, InternalLine>(false, rightLineList[lineID].Item2);
                         }
                         RepopulateDeletionMatrixes();
-                        //TODO: For the person implementing overlay: Add check if overlay needs to be added
                         programPresenter.UpdateRightLines(rightLineList);
                     }
                 }
             }
-        }
-        /*
-        /// <summary>
-        /// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
-        /// </summary>
-        /// <param name="CanvasSize">The size of the canvas</param>
-        public void UpdateSizes(ImageDimension CanvasSize)
-        {
-            if (rightImageWithoutOverlay != null)
+            /*
+            if (programPresenter.IsMousePressed()) //TODO only use if optitrack is in use (currently only available on different branch)
             {
-                int widthImage = rightImageSize.Width;
-                int heightImage = rightImageSize.Height;
-                int widthBox = CanvasSize.Width;
-                int heightBox = CanvasSize.Height;
-
-                float imageRatio = (float)widthImage / (float)heightImage;
-                float containerRatio = (float)widthBox / (float)heightBox;
-                float zoomFactor = 0;
-                if (imageRatio >= containerRatio)
+                if(CentimeterGridPassed(previousCursorPosition, currentCursorPosition)) //TODO replace with optitrack coordinates (and introduce a buffer for previous real-world coordinates)
                 {
-                    //Image is wider than it is high
-                    zoomFactor = (float)widthImage / (float)widthBox;
+                    LocalArmbandInterface.Actuate(0, TACTILE_SURFACE_FEEDBACK_INTENSITY, TACTILE_SURFACE_FEEDBACK_DURATION);
                 }
-                else
-                {
-                    //Image is higher than it is wide
-                    zoomFactor = (float)heightImage / (float)heightBox;
-                }
-                markerRadius = (int)(10 * zoomFactor);
-                deletionRadius = (int)(5 * zoomFactor);
             }
+             */
+        }
+
+        /// <summary>
+        /// checks if the centimeter grid has been passed since the last tick and a vibrotactile feedback has to be sent to the user
+        /// a high enough tick rate must be ensured to provide a useful feedback and avoid skipping necessary feedback
+        /// </summary>
+        /// <param name="previousCursorPosition">the curser position in real world coordinates (obtained from Optitrack) at the last tick</param>
+        /// <param name="currentCursorPosition">the curser position in real world coordinates (obtained from Optitrack) at the current tick</param>
+        /// <returns>true iff the grid has been passed</returns>
+        private bool CentimeterGridPassed(Point previousCursorPositionRealWorldCoordinates, Point currentCursorPositionRealWorldCoordinates)
+        {
+            //truncate coordiates to int after converting to centimeters (from meters), 
+            if ((int)(previousCursorPositionRealWorldCoordinates.X * 100) != (int)(currentCursorPositionRealWorldCoordinates.X * 100)) return true;
+            if ((int)(previousCursorPositionRealWorldCoordinates.Y * 100) != (int)(currentCursorPositionRealWorldCoordinates.Y * 100)) return true;
+            return false;
         }
-        */
+
+        /*
+/// <summary>
+/// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
+/// </summary>
+/// <param name="CanvasSize">The size of the canvas</param>
+public void UpdateSizes(ImageDimension CanvasSize)
+{
+   if (rightImageWithoutOverlay != null)
+   {
+       int widthImage = rightImageSize.Width;
+       int heightImage = rightImageSize.Height;
+       int widthBox = CanvasSize.Width;
+       int heightBox = CanvasSize.Height;
+
+       float imageRatio = (float)widthImage / (float)heightImage;
+       float containerRatio = (float)widthBox / (float)heightBox;
+       float zoomFactor = 0;
+       if (imageRatio >= containerRatio)
+       {
+           //Image is wider than it is high
+           zoomFactor = (float)widthImage / (float)widthBox;
+       }
+       else
+       {
+           //Image is higher than it is wide
+           zoomFactor = (float)heightImage / (float)heightBox;
+       }
+       markerRadius = (int)(10 * zoomFactor);
+       deletionRadius = (int)(5 * zoomFactor);
+   }
+}
+*/
 
         /// <summary>
         /// If there is unsaved progress.

+ 290 - 94
SketchAssistant/SketchAssistantWPF/MVP_Presenter.cs

@@ -8,6 +8,8 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using System.Windows.Media;
 using System.Windows.Shapes;
+using OptiTrack;
+using System.Windows.Ink;
 
 namespace SketchAssistantWPF
 {
@@ -25,14 +27,22 @@ namespace SketchAssistantWPF
         /// A dictionary connecting the id of an InternalLine with the respective Polyline in the right canvas.
         /// </summary>
         Dictionary<int, Shape> rightPolyLines;
-
-        ImageDimension CanvasSizeLeft = new ImageDimension(0,0);
-
-        ImageDimension CanvasSizeRight = new ImageDimension(0, 0);
-
-        ImageDimension ImageSizeLeft = new ImageDimension(0, 0);
-
-        ImageDimension ImageSizeRight = new ImageDimension(0, 0);
+        /// <summary>
+        /// The actual size of the left canvas.
+        /// </summary>
+        ImageDimension canvasSizeLeft = new ImageDimension(0, 0);
+        /// <summary>
+        /// The actual size of the right canvas.
+        /// </summary>
+        ImageDimension canvasSizeRight = new ImageDimension(0, 0);
+        /// <summary>
+        /// A list of line similarities, resulting in the similarity of the whole image.
+        /// </summary>
+        List<double> imageSimilarity = new List<double>();
+        /// <summary>
+        /// The lines in the left canvas.
+        /// </summary>
+        List<InternalLine> leftLines = new List<InternalLine>();
 
         /*******************/
         /*** ENUMERATORS ***/
@@ -40,7 +50,6 @@ namespace SketchAssistantWPF
 
         public enum MouseAction
         {
-            Click,
             Down,
             Up,
             Up_Invalid,
@@ -61,7 +70,6 @@ namespace SketchAssistantWPF
         {
             programView = form;
             programModel = new MVP_Model(this);
-            //Initialize Class Variables
             fileImporter = new FileImporter();
         }
 
@@ -76,57 +84,18 @@ namespace SketchAssistantWPF
         /// <param name="rightPBS">The new size of the left picture box.</param>
         public void Resize(Tuple<int, int> leftPBS, Tuple<int, int> rightPBS)
         {
-            CanvasSizeLeft.ChangeDimension(leftPBS.Item1, leftPBS.Item2);
-            CanvasSizeRight.ChangeDimension(rightPBS.Item1, rightPBS.Item2);
-            //programModel.UpdateSizes(CanvasSizeRight);
-            programModel.ResizeEvent(CanvasSizeLeft, CanvasSizeRight);
-        }
-
-        /// <summary>
-        /// Display a new FileDialog to load a collection of lines.
-        /// </summary>
-        public void ExamplePictureToolStripMenuItemClick()
-        {
-            var okToContinue = true;
-            if (programModel.HasUnsavedProgress())
-            {
-                okToContinue = programView.ShowWarning("You have unsaved progress. Continue?");
-            }
-            if (okToContinue)
-            {
-                var fileNameTup = programView.openNewDialog("Interactive Sketch-Assistant Drawing|*.isad");
-                if (!fileNameTup.Item1.Equals("") && !fileNameTup.Item2.Equals(""))
-                {
-                    programView.SetToolStripLoadStatus(fileNameTup.Item2);
-                    try
-                    {
-                        Tuple<int, int, List<InternalLine>> values = fileImporter.ParseISADInputFile(fileNameTup.Item1);
-                        programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
-
-                        programModel.ResetRightImage();
-                        programModel.CanvasActivated();
-                        programModel.ChangeState(true);
-                        programView.EnableTimer();
-                        ClearRightLines();
-                    }
-                    catch (FileImporterException ex)
-                    {
-                        programView.ShowInfoMessage(ex.ToString());
-                    }
-                    catch (Exception ex)
-                    {
-                        programView.ShowInfoMessage("exception occured while trying to load interactive sketch-assistant drawing file:\n\n" + ex.ToString() + "\n\n" + ex.StackTrace);
-                    }
-                }
-            }
+            canvasSizeLeft.ChangeDimension(leftPBS.Item1, leftPBS.Item2);
+            canvasSizeRight.ChangeDimension(rightPBS.Item1, rightPBS.Item2);
+            programModel.ResizeEvent(canvasSizeRight);
         }
 
         /// <summary>
         /// Display a new FileDialog to a svg drawing.
         /// </summary>
-        public void SVGToolStripMenuItemClick()
+        /// <returns>True if loading was a success</returns>
+        public bool SVGToolStripMenuItemClick()
         {
-            var okToContinue = true;
+            var okToContinue = true; bool returnval = false;
             if (programModel.HasUnsavedProgress())
             {
                 okToContinue = programView.ShowWarning("You have unsaved progress. Continue?");
@@ -140,12 +109,15 @@ namespace SketchAssistantWPF
                     try
                     {
                         Tuple<int, int, List<InternalLine>> values = fileImporter.ParseSVGInputFile(fileNameTup.Item1, programModel.leftImageBoxWidth, programModel.leftImageBoxHeight);
+                        values.Item3.ForEach(line => line.MakePermanent(0)); //Make all lines permanent
                         programModel.SetLeftLineList(values.Item1, values.Item2, values.Item3);
+                        programModel.ResizeEvent(canvasSizeRight);
                         programModel.ResetRightImage();
                         programModel.CanvasActivated();
                         programModel.ChangeState(true);
                         programView.EnableTimer();
                         ClearRightLines();
+                        returnval = true;
                     }
                     catch (FileImporterException ex)
                     {
@@ -157,6 +129,7 @@ namespace SketchAssistantWPF
                     }
                 }
             }
+            return returnval;
         }
 
         /// <summary>
@@ -168,6 +141,35 @@ namespace SketchAssistantWPF
             programModel.ChangeState(NowDrawing);
         }
 
+        /// <summary>
+        /// Gets whether the program is in drawing state or not.
+        /// </summary>
+        /// <returns></returns>
+        public bool GetDrawingState()
+        {
+            return programModel.inDrawingMode;
+        }
+
+        /// <summary>
+        /// Gets whether Optitrack is in use or not.
+        /// </summary>
+        /// <returns>Return optiTrackInUse</returns>
+        public bool GetOptitrackActive()
+        {
+            return programModel.optiTrackInUse;
+        }
+
+        /// <summary>
+        /// Pass-through function to change the OptiTrack-in-use state of the model
+        /// </summary>
+        public void ChangeOptiTrack(bool usingOptiTrack)
+        {
+            if (programModel.optitrackAvailable)
+            {
+                programModel.SetOptiTrack(usingOptiTrack);
+            }
+        }
+
         /// <summary>
         /// Pass-trough function to undo an action.
         /// </summary>
@@ -204,7 +206,7 @@ namespace SketchAssistantWPF
             }
             if (okToContinue)
             {
-                programModel.ResizeEvent(CanvasSizeLeft, CanvasSizeRight);
+                programModel.ResizeEvent(canvasSizeRight);
                 programModel.ResetRightImage();
                 programModel.CanvasActivated();
                 programModel.ChangeState(true);
@@ -217,7 +219,7 @@ namespace SketchAssistantWPF
         /// Pass-trough when the mouse is moved.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
+        /// <param name="position">The position of the mouse.</param>
         public void MouseEvent(MouseAction mouseAction, Point position)
         {
             switch (mouseAction)
@@ -234,27 +236,37 @@ namespace SketchAssistantWPF
         /// Pass-trough function that calls the correct Mouse event of the model, when the mouse is clicked.
         /// </summary>
         /// <param name="mouseAction">The action which is sent by the View.</param>
-        /// <param name="e">The Mouse event arguments.</param>
-        public void MouseEvent(MouseAction mouseAction)
+        /// <param name="strokes">The Strokes.</param>
+        public void MouseEvent(MouseAction mouseAction, StrokeCollection strokes)
         {
-            switch (mouseAction)
+
+            if (!programModel.optiTrackInUse)
             {
-                case MouseAction.Click:
-                    programModel.MouseDown();
-                    programModel.Tick();
-                    programModel.MouseUp(true);
-                    break;
-                case MouseAction.Down:
-                    programModel.MouseDown();
-                    break;
-                case MouseAction.Up:
-                    programModel.MouseUp(true);
-                    break;
-                case MouseAction.Up_Invalid:
-                    programModel.MouseUp(false);
-                    break;
-                default:
-                    break;
+                switch (mouseAction)
+                {
+                    case MouseAction.Down:
+                        programModel.StartNewLine();
+                        break;
+                    case MouseAction.Up:
+                        if (strokes.Count > 0)
+                        {
+                            StylusPointCollection sPoints = strokes.First().StylusPoints;
+                            List<Point> points = new List<Point>();
+                            foreach (StylusPoint p in sPoints)
+                                points.Add(new Point(p.X, p.Y));
+                            programModel.FinishCurrentLine(points);
+                        }
+                        else
+                        {
+                            programModel.FinishCurrentLine(true);
+                        }
+                        break;
+                    case MouseAction.Up_Invalid:
+                        programModel.FinishCurrentLine(false);
+                        break;
+                    default:
+                        break;
+                }
             }
         }
 
@@ -271,6 +283,18 @@ namespace SketchAssistantWPF
             return programView.GetCursorPosition();
         }
 
+        /// <summary>
+        /// Updates the currentline
+        /// </summary>
+        /// <param name="linepoints">The points of the current line.</param>
+        public void UpdateCurrentLine(List<Point> linepoints)
+        {
+            Polyline currentLine = new Polyline();
+            currentLine.Stroke = Brushes.Black;
+            currentLine.Points = new PointCollection(linepoints);
+            programView.DisplayCurrLine(currentLine);
+        }
+
         /// <summary>
         /// Clears all Lines in the right canvas.
         /// </summary>
@@ -278,6 +302,8 @@ namespace SketchAssistantWPF
         {
             programView.RemoveAllRightLines();
             rightPolyLines = new Dictionary<int, Shape>();
+            //Reset the similarity display
+            UpdateSimilarityScore(Double.NaN);
         }
 
         /// <summary>
@@ -285,7 +311,7 @@ namespace SketchAssistantWPF
         /// </summary>
         public void UpdateRightLines(List<Tuple<bool, InternalLine>> lines)
         {
-            foreach(Tuple<bool, InternalLine> tup in lines)
+            foreach (Tuple<bool, InternalLine> tup in lines)
             {
                 var status = tup.Item1;
                 var line = tup.Item2;
@@ -307,21 +333,22 @@ namespace SketchAssistantWPF
                 }
                 SetVisibility(rightPolyLines[line.GetID()], status);
             }
+            //Calculate similarity scores 
+            UpdateSimilarityScore(Double.NaN); var templist = lines.Where(tup => tup.Item1).ToList();
+            if (leftLines.Count > 0)
+            {
+                for (int i = 0; i < leftLines.Count; i++)
+                {
+                    if (templist.Count == i) break;
+                    UpdateSimilarityScore(GeometryCalculator.CalculateSimilarity(templist[i].Item2, leftLines[i]));
+                }
+            }
+            else if (templist.Count > 1)
+            {
+                UpdateSimilarityScore(GeometryCalculator.CalculateSimilarity(templist[templist.Count - 2].Item2, templist[templist.Count - 1].Item2));
+            }
         }
 
-        /*
-        /// <summary>
-        /// Updates the currentline
-        /// </summary>
-        /// <param name="linepoints">The points of the current line.</param>
-        public void UpdateCurrentLine(List<Point> linepoints)
-        {
-            Polyline currentLine = new Polyline();
-            currentLine.Stroke = Brushes.Black;
-            currentLine.Points = new PointCollection(linepoints);
-            programView.DisplayCurrLine(currentLine);
-        }
-        */
         /// <summary>
         /// A function to update the displayed lines in the left canvas.
         /// </summary>
@@ -337,6 +364,8 @@ namespace SketchAssistantWPF
             }
             programView.SetCanvasState("LeftCanvas", true);
             programView.SetCanvasState("RightCanvas", true);
+
+            leftLines = lines;
         }
 
         /// <summary>
@@ -348,23 +377,49 @@ namespace SketchAssistantWPF
         /// <param name="canRedo">If actions in the model can be redone</param>
         /// <param name="canvasActive">If the right canvas is active</param>
         /// <param name="graphicLoaded">If an image is loaded in the model</param>
-        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive, bool graphicLoaded)
+        /// <param name="optiTrackAvailable">If there is an optitrack system available</param>
+        /// <param name="optiTrackInUse">If the optitrack system is active</param>
+        public void UpdateUIState(bool inDrawingMode, bool canUndo, bool canRedo, bool canvasActive,
+                bool graphicLoaded, bool optiTrackAvailable, bool optiTrackInUse)
         {
             Dictionary<String, MainWindow.ButtonState> dict = new Dictionary<String, MainWindow.ButtonState> {
                 {"canvasButton", MainWindow.ButtonState.Enabled }, {"drawButton", MainWindow.ButtonState.Disabled}, {"deleteButton",MainWindow.ButtonState.Disabled },
-                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}};
+                {"undoButton", MainWindow.ButtonState.Disabled },{"redoButton",  MainWindow.ButtonState.Disabled}, {"drawWithOptiButton", MainWindow.ButtonState.Disabled}};
 
             if (canvasActive)
             {
                 if (inDrawingMode)
                 {
-                    dict["drawButton"] = MainWindow.ButtonState.Active;
-                    dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    if (optiTrackAvailable)
+                    {
+                        if (optiTrackInUse)
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Enabled;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Active;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                        else
+                        {
+                            dict["drawButton"] = MainWindow.ButtonState.Active;
+                            dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                            dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                        }
+                    }
+                    else
+                    {
+                        dict["drawButton"] = MainWindow.ButtonState.Active;
+                        dict["deleteButton"] = MainWindow.ButtonState.Enabled;
+                    }
+
                 }
                 else
                 {
                     dict["drawButton"] = MainWindow.ButtonState.Enabled;
                     dict["deleteButton"] = MainWindow.ButtonState.Active;
+                    if (optiTrackAvailable)
+                    {
+                        dict["drawWithOptiButton"] = MainWindow.ButtonState.Enabled;
+                    }
                 }
                 if (canUndo) { dict["undoButton"] = MainWindow.ButtonState.Enabled; }
                 if (canRedo) { dict["redoButton"] = MainWindow.ButtonState.Enabled; }
@@ -387,6 +442,17 @@ namespace SketchAssistantWPF
             programView.ShowInfoMessage(msg);
         }
 
+        /// <summary>
+        /// Pass-through function to desplay an Warning message in the view
+        /// </summary>
+        /// <param name="msg">The message.</param>
+
+        /// <returns>True if the user confirms (Yes), negative if he doesn't (No)</returns>
+        public bool PassWarning(String msg)
+        {
+            return programView.ShowWarning(msg);
+        }
+
         /// <summary>
         /// Pass-trough function to update the display of the last action taken.
         /// </summary>
@@ -405,10 +471,140 @@ namespace SketchAssistantWPF
             return programView.IsMousePressed();
         }
 
+        public void PassOptiTrackMessage(String stringToPass)
+        {
+            programView.SetOptiTrackText(stringToPass);
+        }
+
+        /// <summary>
+        /// Update the similarity score displayed in the UI.
+        /// </summary>
+        /// <param name="score">Score will be reset if NaN is passed, 
+        /// will be ignored if the score is not between 0 and 1</param>
+        public void UpdateSimilarityScore(double score)
+        {
+            if (Double.IsNaN(score))
+            {
+                imageSimilarity.Clear();
+                programView.SetImageSimilarityText("");
+            }
+            else
+            {
+                if (score >= 0 && score <= 1) imageSimilarity.Add(score);
+                programView.SetImageSimilarityText((imageSimilarity.Sum() / imageSimilarity.Count).ToString());
+            }
+        }
+
+        /// <summary>
+        /// Change the color of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="color">The color of the item</param>
+        public void SetOverlayColor(String name, Brush color)
+        {
+            Shape shape = new Ellipse();
+            if(((MainWindow)programView).overlayDictionary.ContainsKey(name))
+            {
+                shape = ((MainWindow)programView).overlayDictionary[name];
+                
+                if(name.Substring(0, 7).Equals("dotLine"))
+                    shape.Stroke = color;
+                else
+                    shape.Fill = color;
+            }
+        }
+
+        /// <summary>
+        /// Change the properties of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="visible">If the element should be visible</param>
+        /// <param name="position">The new position of the element</param>
+        public void SetOverlayStatus(String name, bool visible, Point position)
+        {
+            Shape shape = new Ellipse(); double visibility = 1; double xDif = 0; double yDif = 0;
+            Point point = ConvertRightCanvasCoordinateToOverlay(position);
+            
+            switch (name)
+            {
+                case "optipoint":
+                    shape = ((MainWindow)programView).overlayDictionary["optipoint"];
+                    visibility = 0.75; xDif = 2.5; yDif = 2.5;
+                    break;
+                case "startpoint":
+                    shape = ((MainWindow)programView).overlayDictionary["startpoint"];
+                    xDif = ((MainWindow)programView).markerRadius; yDif = xDif;
+                    break;
+                case "endpoint":
+                    shape = ((MainWindow)programView).overlayDictionary["endpoint"];
+                    xDif = ((MainWindow)programView).markerRadius; yDif = xDif;
+                    break;
+                default:
+                    Console.WriteLine("Unknown Overlay Item. Please check in MVP_Presenter if this item exists.");
+                    return;
+            }
+            if (visible) shape.Opacity = visibility;
+            else shape.Opacity = 0.00001;
+            shape.Margin = new Thickness(point.X - xDif, point.Y - yDif, 0, 0);
+        }
+
+        /// <summary>
+        /// Change the properties of an overlay item.
+        /// </summary>
+        /// <param name="name">The name of the item in the overlay dictionary</param>
+        /// <param name="visible">If the element should be visible</param>
+        /// <param name="pos1">The new position of the element</param>
+        /// <param name="pos2">The new position of the element</param>
+        public void SetOverlayStatus(String name, bool visible, Point pos1, Point pos2)
+        {
+            Shape shape = new Ellipse();
+            switch (name.Substring(0, 7))
+            {
+                case "dotLine":
+                    if (((MainWindow)programView).overlayDictionary.ContainsKey(name))
+                    {
+                        shape = ((MainWindow)programView).overlayDictionary[name];
+                        break;
+                    }
+                    goto default;
+                default:
+                    SetOverlayStatus(name, visible, pos1);
+                    return;
+            }
+            if (visible) shape.Opacity = 1;
+            else shape.Opacity = 0.00001;
+            Point p1 = ConvertRightCanvasCoordinateToOverlay(pos1); Point p2 = ConvertRightCanvasCoordinateToOverlay(pos2);
+            ((Line)shape).X1 = p1.X; ((Line)shape).X2 = p2.X; ((Line)shape).Y1 = p1.Y; ((Line)shape).Y2 = p2.Y;
+        }
+
+        /// <summary>
+        /// Move the optitrack pointer to a new position.
+        /// </summary>
+        /// <param name="position">Position relative to the Rightcanvas</param>
+        public void MoveOptiPoint(Point position)
+        {
+            Point point = ConvertRightCanvasCoordinateToOverlay(position);
+            Shape shape = ((MainWindow)programView).overlayDictionary["optipoint"];
+            shape.Margin = new Thickness(point.X - 2.5, point.Y - 2.5, 0, 0);
+        }
+
         /*************************/
         /*** HELPING FUNCTIONS ***/
         /*************************/
 
+        /// <summary>
+        /// Convert point relative to the right canvas to a point relative to the overlay canvas.
+        /// </summary>
+        /// <param name="p">The point to convert.</param>
+        /// <returns>The respective overlay canvas.</returns>
+        private Point ConvertRightCanvasCoordinateToOverlay(Point p)
+        {
+            MainWindow main = (MainWindow)programView;
+            double xDif = (main.CanvasLeftEdge.ActualWidth + main.LeftCanvas.ActualWidth + main.CanvasSeperator.ActualWidth);
+            double yDif = (main.ButtonToolbar.ActualHeight);
+            return new Point(p.X + xDif, p.Y + yDif);
+        }
+
         /// <summary>
         /// Sets the visibility of a polyline.
         /// </summary>

+ 15 - 0
SketchAssistant/SketchAssistantWPF/MVP_View.cs

@@ -5,6 +5,7 @@ using System.Text;
 using System.Threading.Tasks;
 using System.Windows;
 using System.Windows.Controls;
+using System.Windows.Ink;
 using System.Windows.Shapes;
 
 namespace SketchAssistantWPF
@@ -120,5 +121,19 @@ namespace SketchAssistantWPF
         /// </summary>
         /// <returns>The cursor Position</returns>
         Point GetCursorPosition();
+
+        /// <summary>
+        /// Sets the contents of the last action taken indicator label.
+        /// </summary>
+        /// <param name="message">The new contents</param>
+        void SetOptiTrackText(String message);
+
+        /// <summary>
+        /// Sets the contents of the status bar label containing