using System;
using System.Collections.Generic;
using System.Windows;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using OptiTrack;
using System.Runtime.InteropServices;
namespace SketchAssistantWPF
{
public class MVP_Model
{
///
/// The Presenter of the MVP-Model.
///
MVP_Presenter programPresenter;
///
/// History of Actions
///
ActionHistory historyOfActions;
///
/// The assistant responsible for the redraw mode
///
//RedrawAssistant redrawAss;
OptiTrackConnector connector;
/***********************/
/*** CLASS VARIABLES ***/
/***********************/
///
/// If the program is in drawing mode.
///
bool inDrawingMode;
///
/// if the program is using OptiTrack
///
public bool optiTrackInUse{ get; private set; }
///
/// Size of deletion area
///
int deletionRadius = 5;
///
/// Size of areas marking endpoints of lines in the redraw mode.
///
int markerRadius = 10;
///
/// The Position of the Cursor in the right picture box
///
Point currentCursorPosition;
///
/// The Previous Cursor Position in the right picture box
///
Point previousCursorPosition;
///
/// Queue for the cursorPositions
///
Queue cursorPositions = new Queue();
///
/// Lookup Matrix for checking postions of lines in the image
///
bool[,] isFilledMatrix;
///
/// Lookup Matrix for getting line ids at a certain postions of the image
///
HashSet[,] linesMatrix;
///
/// List of items which will be overlayed over the right canvas.
///
List>> overlayItems;
///
/// Width of the LeftImageBox.
///
public int leftImageBoxWidth;
///
/// Height of the LeftImageBox.
///
public int leftImageBoxHeight;
///
/// Width of the RightImageBox.
///
public int rightImageBoxWidth;
///
/// Height of the RightImageBox.
///
public int rightImageBoxHeight;
public ImageDimension leftImageSize { get; private set; }
public ImageDimension rightImageSize { get; private set; }
///
/// Indicates whether or not the canvas on the right side is active.
///
public bool canvasActive {get; set;}
///
/// Indicates if there is a graphic loaded in the left canvas.
///
public bool graphicLoaded { get; set; }
//TODO: calibrate
double OPTITRACK_X_OFFSET = 0.7878 ;
double OPTITRACK_Y_OFFSET = 0.7977 ;
double OPTITRACK_CANVAS_HEIGHT = 1.29;
double OPTITRACK_X_SCALE = -0.254 * (((1.816 / 0.0254) * 96) / (1.816));
double OPTITRACK_Y_SCALE = 0.254 * (((1.360 / 0.0254) * 96) / (1.360));
Image rightImageWithoutOverlay;
List leftLineList;
List> rightLineList;
List currentLine = new List();
public MVP_Model(MVP_Presenter presenter)
{
programPresenter = presenter;
historyOfActions = new ActionHistory();
//redrawAss = new RedrawAssistant();
//overlayItems = new List>>();
rightLineList = new List>();
canvasActive = false;
UpdateUI();
rightImageSize = new ImageDimension(0, 0);
leftImageSize = new ImageDimension(0, 0);
connector = new OptiTrackConnector();
armband = new Armband();
if (connector.Init(@"C:\Users\videowall-pc-user\Documents\BP-SketchAssistant\SketchAssistant\optitrack_setup.ttp"))
{
connector.StartTracking(getOptiTrackPosition);
}
}
[DllImport("user32.dll", EntryPoint = "SetCursorPos")]
private static extern bool SetCursorPos(int X, int Y);
//[DllImport("user32.dll")]
//public static extern void mouse_event(int dwFlags, int dx, int dy, int cButtons, int dwExtraInfo);
public enum MouseEventType : int
{
LeftDown = 0x02,
LeftUp = 0x04,
RightDown = 0x08,
RightUp = 0x10
}
/**************************/
/*** INTERNAL FUNCTIONS ***/
/**************************/
///
/// Change the status of whether or not the lines are shown.
///
/// The HashSet containing the affected Line IDs.
/// True if the lines should be shown, false if they should be hidden.
private void ChangeLines(HashSet lines, bool shown)
{
foreach (int lineId in lines)
{
if (lineId <= rightLineList.Count - 1 && lineId >= 0)
{
rightLineList[lineId] = new Tuple(shown, rightLineList[lineId].Item2);
}
}
}
///
/// A function that populates the matrixes needed for deletion detection with line data.
///
private void RepopulateDeletionMatrixes()
{
if (canvasActive)
{
isFilledMatrix = new bool[rightImageSize.Width, rightImageSize.Height];
linesMatrix = new HashSet[rightImageSize.Width, rightImageSize.Height];
foreach (Tuple lineTuple in rightLineList)
{
if (lineTuple.Item1)
{
lineTuple.Item2.PopulateMatrixes(isFilledMatrix, linesMatrix);
}
}
}
}
///
/// Tells the Presenter to Update the UI
///
private void UpdateUI()
{
programPresenter.UpdateUIState(inDrawingMode, historyOfActions.CanUndo(), historyOfActions.CanRedo(), canvasActive, graphicLoaded, optiTrackInUse);
}
///
/// A function that checks the deletion matrixes at a certain point
/// and returns all Line ids at that point and in a square around it in a certain range.
///
/// The point around which to check.
/// The range around the point. If range is 0, only the point is checked.
/// A List of all lines.
private HashSet CheckDeletionMatrixesAroundPoint(Point p, int range)
{
HashSet returnSet = new HashSet();
foreach (Point pnt in GeometryCalculator.FilledCircleAlgorithm(p, (int)range))
{
if (pnt.X >= 0 && pnt.Y >= 0 && pnt.X < rightImageSize.Width && pnt.Y < rightImageSize.Height)
{
if (isFilledMatrix[(int)pnt.X, (int)pnt.Y])
{
returnSet.UnionWith(linesMatrix[(int)pnt.X, (int)pnt.Y]);
}
}
}
return returnSet;
}
/********************************************/
/*** FUNCTIONS TO INTERACT WITH PRESENTER ***/
/********************************************/
///
/// A function to update the dimensions of the left and right canvas when the window is resized.
///
/// The size of the left canvas.
/// The size of the right canvas.
public void ResizeEvent(ImageDimension LeftCanvas, ImageDimension RightCanvas)
{
if(LeftCanvas.Height >= 0 && LeftCanvas.Width>= 0) { leftImageSize = LeftCanvas; }
if(RightCanvas.Height >= 0 && RightCanvas.Width >= 0) { rightImageSize = RightCanvas; }
RepopulateDeletionMatrixes();
}
///
/// A function to reset the right image.
///
public void ResetRightImage()
{
rightLineList.Clear();
programPresenter.PassLastActionTaken(historyOfActions.Reset());
programPresenter.ClearRightLines();
}
///
/// The function to set the left image.
///
/// The width of the left image.
/// The height of the left image.
/// The List of Lines to be displayed in the left image.
public void SetLeftLineList(int width, int height, List listOfLines)
{
leftImageSize = new ImageDimension(width, height);
rightImageSize = new ImageDimension(width, height);
leftLineList = listOfLines;
graphicLoaded = true;
programPresenter.UpdateLeftLines(leftLineList);
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>();
*/
}
///
/// A function to tell the model a new canvas was activated.
///
public void CanvasActivated()
{
canvasActive = true;
RepopulateDeletionMatrixes();
UpdateUI();
}
///
/// Will undo the last action taken, if the action history allows it.
///
public void Undo()
{
if (historyOfActions.CanUndo())
{
HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
SketchAction.ActionType undoAction = historyOfActions.GetCurrentAction().GetActionType();
switch (undoAction)
{
case SketchAction.ActionType.Delete:
//Deleted Lines need to be shown
ChangeLines(affectedLines, true);
break;
case SketchAction.ActionType.Draw:
//Drawn lines need to be hidden
ChangeLines(affectedLines, false);
break;
default:
break;
}
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
}
RepopulateDeletionMatrixes();
programPresenter.PassLastActionTaken(historyOfActions.MoveAction(true));
UpdateUI();
}
///
/// Will redo the last action undone, if the action history allows it.
///
public void Redo()
{
if (historyOfActions.CanRedo())
{
programPresenter.PassLastActionTaken(historyOfActions.MoveAction(false));
HashSet affectedLines = historyOfActions.GetCurrentAction().GetLineIDs();
SketchAction.ActionType redoAction = historyOfActions.GetCurrentAction().GetActionType();
switch (redoAction)
{
case SketchAction.ActionType.Delete:
//Deleted Lines need to be redeleted
ChangeLines(affectedLines, false);
break;
case SketchAction.ActionType.Draw:
//Drawn lines need to be redrawn
ChangeLines(affectedLines, true);
break;
default:
break;
}
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
RepopulateDeletionMatrixes();
}
UpdateUI();
}
///
/// The function called by the Presenter to change the drawing state of the program.
///
/// The new drawingstate of the program
public void ChangeState(bool nowDrawing)
{
inDrawingMode = nowDrawing;
UpdateUI();
}
public void ChangeOptiTrack(bool usingOptiTrack)
{
optiTrackInUse = usingOptiTrack;
}
///
/// Updates the current cursor position of the model.
///
/// The new cursor position
public void SetCurrentCursorPosition(Point p)
{
if (!optiTrackInUse) currentCursorPosition = p;
}
///
/// Updates the current cursor position of the model.
///
/// The new cursor position
public void SetCurrentFingerPosition(Point p)
{
Console.WriteLine("raw coordinates: " + p.X + ";" + p.Y);
Point correctedPoint = ConvertTo96thsOfInch(p);
Console.WriteLine(correctedPoint.X + "," + correctedPoint.Y);
currentCursorPosition = correctedPoint;
}
private Point ConvertTo96thsOfInch(Point p)
{
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);
}
private Point ConvertToPixel(Point p)
{
double xCoordinate = (p.X - OPTITRACK_X_OFFSET) * -1 * (/*Anzahl Pixel X-Richtung*/0 / (1.816)) + 0/*1/2 * x-richtung pixel*/); //TODO
double yCoordinate = (((OPTITRACK_CANVAS_HEIGHT + 0/*meter von oberer Rand Leinwand zu oberer Rand Bildschirm*/) - (p.Y - OPTITRACK_Y_OFFSET))) * (/*Anzahl Pixel Y-Richtung*/0 / (1.360));
return new Point(xCoordinate, yCoordinate);
}
///
/// Start a new Line, when the Mouse is pressed down.
///
public void StartNewLine()
{
if (inDrawingMode)
{
currentLine.Clear();
currentLine.Add(currentCursorPosition);
}
}
///
/// Finish the current Line, when the pressed Mouse is released.
///
public void FinishCurrentLine()
{
foreach (Point p in currentLine)
{
Console.WriteLine(p.X + ";" + p.Y);
}
if (inDrawingMode && currentLine.Count > 0)
{
InternalLine newLine = new InternalLine(currentLine, rightLineList.Count);
rightLineList.Add(new Tuple(true, newLine));
newLine.PopulateMatrixes(isFilledMatrix, linesMatrix);
programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Draw, newLine.GetID())));
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
currentLine.Clear();
programPresenter.UpdateCurrentLine(currentLine);
}
UpdateUI();
}
public float optiTrackX;
public float optiTrackY;
public float optiTrackZ;
private bool optiTrackInsideDrawingZone = false;
private double WARNING_ZONE_BOUNDARY= 0.10; //5cm
private Armband armband;
void getOptiTrackPosition(OptiTrack.Frame frame)
{
optiTrackX = frame.Trackables[0].X;
optiTrackY = frame.Trackables[0].Y;
optiTrackZ = frame.Trackables[0].Z;
}
///
/// Method to be called every tick. Updates the current Line, or checks for Lines to delete, depending on the drawing mode.
///
public void Tick()
{
if (optiTrackInUse)
{
if (CheckInsideDrawingZone(optiTrackZ))
{
SetCurrentFingerPosition(new Point(optiTrackX, optiTrackY));
if (!optiTrackInsideDrawingZone)
{
optiTrackInsideDrawingZone = true;
StartNewLine();
Console.WriteLine("new line begun");
}
if(optiTrackZ > WARNING_ZONE_BOUNDARY)
{
armband.pushForward();
}
else if(optiTrackZ < -1 * WARNING_ZONE_BOUNDARY)
{
armband.pushBackward();
}
}
else
{
if (optiTrackInsideDrawingZone)
{
optiTrackInsideDrawingZone = false;
FinishCurrentLine();
Console.WriteLine("line finished");
}
}
projectPointOntoScreen(optiTrackX, optiTrackY);
}
if (cursorPositions.Count > 0) { previousCursorPosition = cursorPositions.Dequeue(); }
else { previousCursorPosition = currentCursorPosition; }
cursorPositions.Enqueue(currentCursorPosition);
//Drawing
if (optiTrackInUse)
{
if (CheckInsideDrawingZone(optiTrackZ))
{
Console.WriteLine("point added");
currentLine.Add(currentCursorPosition);
programPresenter.UpdateCurrentLine(currentLine);
}
}
else if (inDrawingMode && programPresenter.IsMousePressed())
{
currentLine.Add(currentCursorPosition);
programPresenter.UpdateCurrentLine(currentLine);
}
//Deleting
if (!inDrawingMode && programPresenter.IsMousePressed())
{
List uncheckedPoints= GeometryCalculator.BresenhamLineAlgorithm(previousCursorPosition, currentCursorPosition);
foreach (Point currPoint in uncheckedPoints)
{
HashSet linesToDelete = CheckDeletionMatrixesAroundPoint(currPoint, deletionRadius);
if (linesToDelete.Count > 0)
{
programPresenter.PassLastActionTaken(historyOfActions.AddNewAction(new SketchAction(SketchAction.ActionType.Delete, linesToDelete)));
foreach (int lineID in linesToDelete)
{
rightLineList[lineID] = new Tuple(false, rightLineList[lineID].Item2);
}
RepopulateDeletionMatrixes();
//TODO: For the person implementing overlay: Add check if overlay needs to be added
programPresenter.UpdateRightLines(rightLineList);
}
}
}
}
private void projectPointOntoScreen(float optiTrackX, float optiTrackY)
{
Point auxiliaryPoint = ConvertToPixel(new Point(optiTrackX, optiTrackY));
SetCursorPos((int)auxiliaryPoint.X, (int)auxiliaryPoint.Y);
}
private bool CheckInsideDrawingZone(float optiTrackZ)
{
if (Math.Abs(optiTrackZ) > WARNING_ZONE_BOUNDARY * 2) return false;
return true;
}
///
/// A helper Function that updates the markerRadius & deletionRadius, considering the size of the canvas.
///
/// The size of the canvas
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);
}
}
///
/// If there is unsaved progress.
///
/// True if there is progress that has not been saved.
public bool HasUnsavedProgress()
{
return !historyOfActions.IsEmpty();
}
}
}