using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using System.IO;
public class GestureRecognizer : MonoBehaviour
{
public bool recording =
true;
public bool anomaliesTesting =
false;
public string templateSaveName;
public int pointsPerGesture =
30;
public float samplingRate =
0.01f;
public bool limitSamples =
false;
public int maxPointsAllowed =
100;
public float standardRatio =
100f;
public float devTightness =
1f;
public float anomaliesFactor =
5f;
private bool gestureStarted;
private bool gestureComplete;
private bool inputReady;
private string gestureFileName =
"gestures.json";
private TwoDPoint startPoint;
private TwoDPoint currentPoint;
private DrawnGesture currentGesture;
private List<TwoDPoint> currentPointList;
private TwoDPoint[] reducedPoints;
private GestureTemplates templates;
private float tempTime =
0f;
private void Awake()
{
}
void Start()
{
LoadTemplates();
varInitialization();
}
#region variable initialization and reset
private void varInitialization()
{
currentPoint =
new TwoDPoint(
0,
0);
startPoint =
new TwoDPoint(
0,
0);
currentPointList =
new List<TwoDPoint>();
currentPointList.Add(
new TwoDPoint(
0,
0));
reducedPoints =
new TwoDPoint[pointsPerGesture];
for (
int i =
0; i < pointsPerGesture; i++)
{
reducedPoints[i] =
new TwoDPoint(
0,
0);
}
gestureStarted =
false;
gestureComplete =
false;
inputReady =
false;
currentGesture =
new DrawnGesture(
"currentGesture", pointsPerGesture);
}
private void varReset()
{
for (
int i =
0; i < pointsPerGesture; i++)
{
reducedPoints[i].SetX(
0);
reducedPoints[i].SetY(
0);
}
currentPointList.Clear();
currentPointList.Add(
new TwoDPoint(
0,
0));
gestureStarted =
false;
gestureComplete =
false;
}
#endregion
void Update()
{
tempTime += Time.deltaTime;
if (Input.GetMouseButton(
0))
{
if (inputReady)
{
if (!gestureStarted)
{
gestureStarted =
true;
StartGesture();
}
if ((!gestureComplete) && (tempTime > samplingRate))
{
tempTime =
0f;
ContinueGesture();
}
if (gestureComplete)
{
EndGesture();
}
}
}
else
{
if (gestureStarted)
{
EndGesture();
}
inputReady =
true;
}
if(Input.GetKeyDown(KeyCode.S))
{
SaveTemplates();
}
}
private void SaveTemplates()
{
if (!Directory.Exists(Application.dataPath +
"/StreamingAssets/"))
Directory.CreateDirectory(Application.dataPath +
"/StreamingAssets/");
string filePath = Application.dataPath +
"/StreamingAssets/" + gestureFileName;
string saveData = JsonUtility.ToJson(templates);
File.WriteAllText(filePath, saveData);
}
private void LoadTemplates()
{
templates =
new GestureTemplates();
string filePath = Path.Combine(Application.streamingAssetsPath, gestureFileName);
if (File.Exists(filePath))
{
string data = File.ReadAllText(filePath);
templates = JsonUtility.FromJson<GestureTemplates>(data);
}
}
private void StartGesture()
{
Debug.Log(
"gesture started");
startPoint.SetX(Input.mousePosition.x);
startPoint.SetY(Input.mousePosition.y);
gestureComplete =
false;
}
private void ContinueGesture()
{
currentPoint.SetX(Input.mousePosition.x - startPoint.GetX());
currentPoint.SetY(Input.mousePosition.y - startPoint.GetY());
currentPointList.Add(
new TwoDPoint(currentPoint.GetX(), currentPoint.GetY()));
if (currentPoint.GetX() > currentGesture.GetMaxX())
{
currentGesture.SetMaxX(currentPoint.GetX());
}
if (currentPoint.GetX() < currentGesture.GetMinX())
{
currentGesture.SetMinX(currentPoint.GetX());
}
if (currentPoint.GetY() > currentGesture.GetMaxY())
{
currentGesture.SetMaxY(currentPoint.GetY());
}
if (currentPoint.GetY() < currentGesture.GetMinY())
{
currentGesture.SetMinY(currentPoint.GetY());
}
if (limitSamples && currentPointList.Count >= maxPointsAllowed)
{
gestureComplete =
true;
Debug.Log(message:
"Gesture Complete!");
}
}
private void EndGesture()
{
if (inputReady) inputReady =
false;
gestureStarted =
false;
gestureComplete =
true;
Rescale(currentGesture);
MapPoints(currentGesture);
if (recording)
{
currentGesture.SetName(templateSaveName);
templates.templates.Add(
new DrawnGesture(currentGesture.GetName(), pointsPerGesture, currentGesture.GetMaxX(), currentGesture.GetMaxY(),
currentGesture.GetMinX(), currentGesture.GetMinY(), currentGesture.GetPoints()));
}
else
{
DrawnGesture m = FindMatch(currentGesture, templates);
Debug.Log(m.GetName());
}
varReset();
}
private void Rescale(DrawnGesture gesture)
{
float scale =
1f;
float xrange = gesture.GetMaxX() - gesture.GetMinX();
float yrange = gesture.GetMaxY() - gesture.GetMinY();
if (xrange >= yrange)
{
scale = standardRatio / (gesture.GetMaxX() - gesture.GetMinX());
}
else
{
scale = standardRatio / (gesture.GetMaxY() - gesture.GetMinY());
}
if (scale !=
1)
{
foreach (TwoDPoint point
in currentPointList)
{
point.SetX(point.GetX() * scale);
point.SetY(point.GetY() * scale);
}
}
}
private void MapPoints(DrawnGesture gesture)
{
reducedPoints[
0].SetX(currentPointList[
0].GetX());
reducedPoints[
0].SetY(currentPointList[
0].GetY());
int newIndex =
1;
float totalDistance = TotalDistance();
float coveredDistance =
0;
float thisDistance =
0;
float idealInterval = totalDistance / pointsPerGesture;
for (
int i =
0; i < currentPointList.Count -
1; i++)
{
thisDistance = PointDistance(currentPointList[i], currentPointList[i +
1]);
bool passedIdeal = (coveredDistance + thisDistance) >= idealInterval;
if (passedIdeal)
{
TwoDPoint reference = currentPointList[i];
while (passedIdeal && newIndex < reducedPoints.Length)
{
float percentNeeded = (idealInterval - coveredDistance) / thisDistance;
if (percentNeeded >
1f) percentNeeded =
1f;
if (percentNeeded <
0f) percentNeeded =
0f;
float new_x = (((
1f - percentNeeded) * reference.GetX()) + (percentNeeded * currentPointList[i +
1].GetX()));
float new_y = (((
1f - percentNeeded) * reference.GetY()) + (percentNeeded * currentPointList[i +
1].GetY()));
reducedPoints[newIndex] =
new TwoDPoint(new_x, new_y);
reference = reducedPoints[newIndex];
newIndex++;
thisDistance = (coveredDistance + thisDistance) - idealInterval;
coveredDistance =
0;
passedIdeal = (coveredDistance + thisDistance) >= idealInterval;
}
coveredDistance = thisDistance;
}
else
{
coveredDistance += thisDistance;
}
gesture.SetPoints(reducedPoints);
}
}
private DrawnGesture
FindMatch(DrawnGesture playerGesture, GestureTemplates templates)
{
float minAvgDifference =
float.MaxValue;
DrawnGesture match =
new DrawnGesture(
"no match", pointsPerGesture);
foreach (DrawnGesture template
in templates.templates)
{
Debug.Log(template.GetName());
float d = AverageDifference(playerGesture, template);
Debug.Log(d.ToString());
if (d < minAvgDifference)
{
minAvgDifference = d;
match = template;
}
}
return match;
}
private float AverageDifference(DrawnGesture playerGesture, DrawnGesture template)
{
int numPoints = playerGesture.GetNumPoints();
if (numPoints != template.GetNumPoints())
{
Debug.Log(
"Number of points differs from templates");
return -
1f;
}
float totalDifference =
0;
for (
int i =
0; i < numPoints; i++)
{
totalDifference += PointDistance(playerGesture.GetPoints()[i], template.GetPoints()[i]);
}
return (totalDifference / numPoints);
}
private float AverageDifferenceWithAnomalies(DrawnGesture playerGesture, DrawnGesture template)
{
int numPoints = playerGesture.GetNumPoints();
if (numPoints != template.GetNumPoints())
{
Debug.Log(
"Number of points differs from templates");
return -
1f;
}
float totalDifference =
0;
float[] sampleDifferences =
new float[numPoints];
float[] sampleDeviations =
new float[numPoints];
float standardDev =
0;
for (
int i =
0; i < numPoints; i++)
{
float thisDistance = PointDistance(playerGesture.GetPoints()[i], template.GetPoints()[i]);
sampleDifferences[i] = thisDistance;
totalDifference += thisDistance;
}
float average = totalDifference / numPoints;
for (
int i =
0; i < numPoints; i++)
{
sampleDeviations[i] = Math.Abs(sampleDifferences[i] - average);
standardDev += sampleDifferences[i];
}
standardDev = standardDev / numPoints;
for (
int i =
0; i < numPoints; i++)
{
if (Math.Abs(sampleDeviations[i]) > devTightness * standardDev)
{
totalDifference -= sampleDifferences[i];
totalDifference += anomaliesFactor * sampleDifferences[i];
}
}
average = totalDifference / numPoints;
return (average);
}
private float TotalDistance()
{
float totalDistance =
0;
for (
int i =
0; i < currentPointList.Count -
1; i++)
{
totalDistance += PointDistance(currentPointList[i], currentPointList[i +
1]);
}
Debug.Log(
"total distance: " + totalDistance);
return totalDistance;
}
private float PointDistance(TwoDPoint a, TwoDPoint b)
{
float xDif = a.GetX() - b.GetX();
float yDif = a.GetY() - b.GetY();
return Mathf.Sqrt((xDif * xDif) + (yDif * yDif));
}
}
[Serializable]
public class GestureTemplates
{
public List<DrawnGesture> templates;
public GestureTemplates()
{
templates =
new List<DrawnGesture>();
}
}
[Serializable]
public class DrawnGesture
{
private TwoDPoint[] points;
private string name;
private float maxX;
private float minX;
private float maxY;
private float minY;
private int numPoints;
public DrawnGesture(
string newName,
int pointsPerGesture)
{
numPoints = pointsPerGesture;
points =
new TwoDPoint[numPoints];
name = newName;
maxX =
0;
maxY =
0;
}
public DrawnGesture(
string newName,
int pointsPerGesture,
float max_x,
float max_y,
float min_x,
float min_y, TwoDPoint[] newPoints)
{
numPoints = pointsPerGesture;
points =
new TwoDPoint[numPoints];
SetPoints(newPoints);
name = newName;
maxX = max_x;
minX = min_x;
maxY = max_y;
minY = min_y;
}
public void Reset()
{
maxX =
0;
minX =
0;
maxY =
0;
minY =
0;
name =
"";
Array.Clear(points,
0, numPoints);
}
public TwoDPoint[]
GetPoints()
{
return points;
}
public void SetPoints(TwoDPoint[] new_points)
{
for (
int i =
0; i < numPoints; i++)
{
points[i] =
new TwoDPoint(new_points[i].GetX(), new_points[i].GetY());
}
}
public string GetName()
{
return name;
}
public void SetName(
string n)
{
name = n;
}
public float GetMaxX()
{
return maxX;
}
public void SetMaxX(
float x)
{
maxX = x;
}
public float GetMaxY()
{
return maxY;
}
public void SetMaxY(
float y)
{
maxY = y;
}
public float GetMinY()
{
return minY;
}
public void SetMinY(
float y)
{
minY = y;
}
public float GetMinX()
{
return minX;
}
public void SetMinX(
float x)
{
minX = x;
}
public int GetNumPoints()
{
return numPoints;
}
public void SetNumPoints(
int n)
{
numPoints = n;
}
}
[Serializable]
public class TwoDPoint
{
private float x;
private float y;
public TwoDPoint(
float startx,
float starty)
{
x = startx;
y = starty;
}
public float GetX()
{
return x;
}
public void SetX(
float new_x)
{
x = new_x;
}
public float GetY()
{
return y;
}
public void SetY(
float new_y)
{
y = new_y;
}
}
============================
๊นํ๋ธ์์ ๋ค์ด๋ฐ์ ํ์ฝ๋์
๋๋ค. ์ด๊ฑฐ ์ json ํ์ผ์ ์ ์ฅํด๋ ๋น๋ฐฐ์ด๋ง ๋ณด์ผ๊น์?
์ ์ฅ๊ณผ ๊ด๋ จ๋ ํจ์๋ SaveTemplate, LoadTemplate์
๋๋ค. ์ ์ฅ๋๋ ๋ด์ฉ์ ๋งจ ์๋ serailize๋ ํด๋์ค๋ค์ด๊ตฌ์.
์๋ ค์ฃผ์๋ฉด ใณ์.
์ ์ฅํ ๋ฉค๋ฒ๋ค public์ผ๋ก ๋ฐ๊พธ์ - dc App
์ด๋ฏธ ํผ๋ธ๋ฆญ์
๋ฆฌ์คํธ์ ๋ด๊ธฐ๋ ์ ๋ณด๋ ํผ๋ธ๋ฆญ์ผ๋ฌ ํ๋ผ๋ ์๋ฆฐ๊ฐ
์ ๋ถ ํผ๋ธ๋ฆญ์ผ๋ก ๋ฐ๊พธ๋๊น ๋๋ค ใณใณ
๊ฒฐ๊ณผ๊ฐ ๋๋ฒ๊ทธ ํ ์คํธ๋ก ๋จผ์ ์ฐ์ด. ์ฝ๋ ๋ณด๊ธฐ ๊ท์ฐฎ์.
๊ฒฐ๊ณผ๋ ๋ฉ์ฉกํ๋ฐ json ํ์ผ์ ์ ์ฅ์ด ์ ๋จ. ์ ์ฅํ๋ฉด templates{[],[],[]} ์ด๋ฐ ํ ์คํธ๋ง ์์.