|
|
cfceb0 |
using System;
|
|
|
cfceb0 |
using System.Collections.Generic;
|
|
|
b82ef4 |
using Assistance.Drawing;
|
|
|
cfceb0 |
|
|
|
cfceb0 |
namespace Assistance {
|
|
|
cfceb0 |
public class Track {
|
|
|
589f9a |
public interface IOwner { }
|
|
|
61bedf |
|
|
|
a62e15 |
public class Handler {
|
|
|
589f9a |
public readonly IOwner owner;
|
|
|
a62e15 |
public readonly Track original;
|
|
|
a62e15 |
public readonly List<track> tracks = new List<track>();
|
|
|
589f9a |
public Handler(IOwner owner, Track original) {
|
|
|
a62e15 |
this.owner = owner;
|
|
|
a62e15 |
this.original = original;
|
|
|
a62e15 |
}
|
|
|
a62e15 |
}
|
|
|
a62e15 |
|
|
|
a62e15 |
public class Modifier {
|
|
|
a62e15 |
public readonly Handler handler;
|
|
|
a62e15 |
public readonly double timeOffset;
|
|
|
a62e15 |
|
|
|
a62e15 |
public Modifier(Handler handler, double timeOffset = 0.0)
|
|
|
a62e15 |
{ this.handler = handler; this.timeOffset = timeOffset; }
|
|
|
589f9a |
public IOwner owner
|
|
|
a62e15 |
{ get { return handler.owner; } }
|
|
|
a62e15 |
public Track original
|
|
|
a62e15 |
{ get { return handler.original; } }
|
|
|
702257 |
public virtual Point calcPoint(double originalIndex) {
|
|
|
702257 |
Point p = original.calcPoint(originalIndex);
|
|
|
7bbf70 |
p.originalIndex = originalIndex;
|
|
|
7bbf70 |
return p;
|
|
|
7bbf70 |
}
|
|
|
a62e15 |
}
|
|
|
a62e15 |
|
|
|
a62e15 |
public struct Point {
|
|
|
a62e15 |
public Assistance.Point position;
|
|
|
a62e15 |
public double pressure;
|
|
|
589f9a |
public Assistance.Point tilt;
|
|
|
a62e15 |
|
|
|
a62e15 |
public double originalIndex;
|
|
|
a62e15 |
public double time;
|
|
|
a62e15 |
public double length;
|
|
|
a62e15 |
|
|
|
a62e15 |
public bool final;
|
|
|
a62e15 |
|
|
|
702257 |
public Point(
|
|
|
702257 |
Assistance.Point position,
|
|
|
702257 |
double pressure = 0.5,
|
|
|
702257 |
Assistance.Point tilt = new Assistance.Point(),
|
|
|
a62e15 |
double originalIndex = 0.0,
|
|
|
a62e15 |
double time = 0.0,
|
|
|
a62e15 |
double length = 0.0,
|
|
|
a62e15 |
bool final = false
|
|
|
a62e15 |
) {
|
|
|
702257 |
this.position = position;
|
|
|
702257 |
this.pressure = pressure;
|
|
|
702257 |
this.tilt = tilt;
|
|
|
a62e15 |
this.originalIndex = originalIndex;
|
|
|
a62e15 |
this.time = time;
|
|
|
a62e15 |
this.length = length;
|
|
|
a62e15 |
this.final = final;
|
|
|
a62e15 |
}
|
|
|
a62e15 |
}
|
|
|
a62e15 |
|
|
|
77e178 |
|
|
|
61bedf |
private static long lastId;
|
|
|
72d2fd |
private readonly List<point> privatePoints = new List<point>();</point></point>
|
|
|
72d2fd |
private int privatePointsRemoved;
|
|
|
72d2fd |
private int privatePointsAdded;
|
|
|
77e178 |
|
|
|
61bedf |
public readonly long id;
|
|
|
c66393 |
public readonly Gdk.Device device;
|
|
|
61bedf |
public readonly long touchId;
|
|
|
a62e15 |
public readonly KeyHistory<gdk.key>.Holder keyHistory;</gdk.key>
|
|
|
a62e15 |
public readonly KeyHistory<uint>.Holder buttonHistory;</uint>
|
|
|
cfceb0 |
|
|
|
a62e15 |
public readonly Modifier modifier;
|
|
|
a62e15 |
|
|
|
a62e15 |
public Handler handler;
|
|
|
77c1c7 |
|
|
|
a62e15 |
public Track(
|
|
|
61bedf |
Gdk.Device device = null,
|
|
|
61bedf |
long touchId = 0,
|
|
|
61bedf |
KeyHistory<gdk.key>.Holder keyHistory = null,</gdk.key>
|
|
|
61bedf |
KeyHistory<uint>.Holder buttonHistory = null</uint>
|
|
|
a62e15 |
) {
|
|
|
61bedf |
this.id = ++lastId;
|
|
|
c66393 |
this.device = device;
|
|
|
61bedf |
this.touchId = touchId;
|
|
|
a62e15 |
this.keyHistory = keyHistory;
|
|
|
a62e15 |
this.buttonHistory = buttonHistory;
|
|
|
c66393 |
}
|
|
|
a62e15 |
|
|
|
61bedf |
public Track(Modifier modifier):
|
|
|
a62e15 |
this( modifier.original.device,
|
|
|
589f9a |
modifier.original.touchId,
|
|
|
a62e15 |
modifier.original.keyHistory.offset( modifier.timeOffset ),
|
|
|
61bedf |
modifier.original.buttonHistory.offset( modifier.timeOffset ) )
|
|
|
a62e15 |
{ this.modifier = modifier; }
|
|
|
77c1c7 |
|
|
|
a62e15 |
public Track original
|
|
|
a62e15 |
{ get { return modifier != null ? modifier.original : null; } }
|
|
|
a62e15 |
public double timeOffset
|
|
|
a62e15 |
{ get { return modifier != null ? modifier.timeOffset : 0.0; } }
|
|
|
720280 |
public long ticks
|
|
|
720280 |
{ get { return keyHistory.ticks; } }
|
|
|
c3ebff |
|
|
|
72d2fd |
public int pointsAdded
|
|
|
72d2fd |
{ get { return privatePointsAdded; } }
|
|
|
72d2fd |
public int pointsRemoved
|
|
|
72d2fd |
{ get { return privatePointsRemoved; } }
|
|
|
72d2fd |
public void resetRemoved()
|
|
|
72d2fd |
{ privatePointsRemoved = 0; }
|
|
|
72d2fd |
public void resetAdded()
|
|
|
72d2fd |
{ privatePointsAdded = 0; }
|
|
|
72d2fd |
public void resetCounters()
|
|
|
72d2fd |
{ resetRemoved(); resetAdded(); }
|
|
|
72d2fd |
public void forceRemoved(int pointsRemoved)
|
|
|
72d2fd |
{ privatePointsRemoved = pointsRemoved; }
|
|
|
72d2fd |
public void forceAdded(int pointsAdded)
|
|
|
72d2fd |
{ privatePointsAdded = pointsAdded; }
|
|
|
72d2fd |
|
|
|
72d2fd |
public bool wasRemoved
|
|
|
72d2fd |
{ get { return pointsRemoved > 0; } }
|
|
|
72d2fd |
public bool wasAdded
|
|
|
72d2fd |
{ get { return pointsAdded > 0; } }
|
|
|
72d2fd |
public bool wasChanged
|
|
|
72d2fd |
{ get { return wasRemoved || wasAdded; } }
|
|
|
72d2fd |
|
|
|
72d2fd |
public bool isEmpty
|
|
|
72d2fd |
{ get { return count == 0; } }
|
|
|
72d2fd |
public int count
|
|
|
72d2fd |
{ get { return privatePoints.Count; } }
|
|
|
72d2fd |
public void remove(int count = 1) {
|
|
|
72d2fd |
if (count > this.count) count = this.count;
|
|
|
72d2fd |
if (count <= 0) return;
|
|
|
72d2fd |
privatePoints.RemoveRange(this.count - count, count);
|
|
|
72d2fd |
privatePointsRemoved += count;
|
|
|
72d2fd |
}
|
|
|
72d2fd |
public void truncate(int count)
|
|
|
72d2fd |
{ remove(this.count - count); }
|
|
|
72d2fd |
public void add(Point p) {
|
|
|
72d2fd |
if (!isEmpty) {
|
|
|
72d2fd |
Point previous = getLast();
|
|
|
72d2fd |
// fix originalIndex
|
|
|
72d2fd |
if (p.originalIndex < previous.originalIndex)
|
|
|
72d2fd |
p.originalIndex = previous.originalIndex;
|
|
|
72d2fd |
// fix time
|
|
|
72d2fd |
p.time = Math.Max(p.time, previous.time + Timer.step);
|
|
|
72d2fd |
// calculate length
|
|
|
72d2fd |
p.length = previous.length + (p.position - previous.position).len();
|
|
|
72d2fd |
}
|
|
|
72d2fd |
privatePoints.Add(p);
|
|
|
72d2fd |
++privatePointsAdded;
|
|
|
72d2fd |
}
|
|
|
72d2fd |
|
|
|
72d2fd |
public Point this[int index]
|
|
|
72d2fd |
{ get { return getPoint(index); } }
|
|
|
72d2fd |
public IEnumerable<point> points</point>
|
|
|
72d2fd |
{ get { return privatePoints; } }
|
|
|
77c1c7 |
|
|
|
a62e15 |
public Track getRoot()
|
|
|
a62e15 |
{ return original == null ? this : original.getRoot(); }
|
|
|
a62e15 |
public int getLevel()
|
|
|
a62e15 |
{ return original == null ? 0 : original.getLevel() + 1; }
|
|
|
c66393 |
|
|
|
702257 |
public Point getFirst()
|
|
|
702257 |
{ return getPoint(0); }
|
|
|
702257 |
public Point getLast()
|
|
|
72d2fd |
{ return getPoint(count - 1); }
|
|
|
a62e15 |
public bool isFinished()
|
|
|
72d2fd |
{ return count > 0 && getLast().final; }
|
|
|
a62e15 |
|
|
|
a62e15 |
public int clampIndex(int index)
|
|
|
72d2fd |
{ return Math.Min(Math.Max(index, 0), count - 1); }
|
|
|
a62e15 |
public int floorIndex(double index, out double frac) {
|
|
|
a62e15 |
int i = (int)Math.Floor(index + Geometry.precision);
|
|
|
72d2fd |
if (i > count - 1)
|
|
|
72d2fd |
{ frac = 0.0; return count - 1; }
|
|
|
a62e15 |
if (i < 0)
|
|
|
a62e15 |
{ frac = 0.0; return 0; }
|
|
|
a62e15 |
frac = Math.Max(0.0, index - (double)i);
|
|
|
a62e15 |
return i;
|
|
|
72b17c |
}
|
|
|
1cdf17 |
public int floorIndexNoClamp(double index)
|
|
|
1cdf17 |
{ return (int)Math.Floor(index + Geometry.precision); }
|
|
|
a62e15 |
public int floorIndex(double index)
|
|
|
1cdf17 |
{ return clampIndex(floorIndexNoClamp(index)); }
|
|
|
1cdf17 |
public int ceilIndexNoClamp(double index)
|
|
|
1cdf17 |
{ return (int)Math.Ceiling(index - Geometry.precision); }
|
|
|
a62e15 |
public int ceilIndex(double index)
|
|
|
1cdf17 |
{ return clampIndex(ceilIndexNoClamp(index)); }
|
|
|
a62e15 |
|
|
|
702257 |
public Point getPoint(int index) {
|
|
|
a62e15 |
index = clampIndex(index);
|
|
|
72d2fd |
return index < 0 ? new Point() : privatePoints[index];
|
|
|
77c1c7 |
}
|
|
|
702257 |
public Point floorPoint(double index, out double frac)
|
|
|
702257 |
{ return getPoint(floorIndex(index, out frac)); }
|
|
|
702257 |
public Point floorPoint(double index)
|
|
|
702257 |
{ return getPoint(floorIndex(index)); }
|
|
|
702257 |
public Point ceilPoint(double index)
|
|
|
702257 |
{ return getPoint(ceilIndex(index)); }
|
|
|
77c1c7 |
|
|
|
702257 |
private delegate double PointFieldGetter(Point p);
|
|
|
702257 |
private double binarySearch(double value, PointFieldGetter getter) {
|
|
|
a62e15 |
// points[a].value <= value < points[b].value
|
|
|
77c1c7 |
|
|
|
72d2fd |
if (isEmpty) return 0.0;
|
|
|
a62e15 |
int a = 0;
|
|
|
72d2fd |
double aa = getter(privatePoints[a]);
|
|
|
72d2fd |
if (value - aa <= 0.5*Geometry.precision) return (double)a;
|
|
|
a62e15 |
|
|
|
72d2fd |
int b = count - 1;
|
|
|
72d2fd |
double bb = getter(privatePoints[b]);
|
|
|
72d2fd |
if (bb - value <= 0.5*Geometry.precision) return (double)b;
|
|
|
77c1c7 |
|
|
|
a62e15 |
while(true) {
|
|
|
a62e15 |
int c = (a + b)/2;
|
|
|
a62e15 |
if (a == c) break;
|
|
|
72d2fd |
double cc = getter(privatePoints[c]);
|
|
|
72d2fd |
if (cc - value > 0.5*Geometry.precision)
|
|
|
a62e15 |
{ b = c; bb = cc; } else { a = c; aa = cc; }
|
|
|
77c1c7 |
}
|
|
|
a62e15 |
|
|
|
72d2fd |
return bb - aa >= 0.5*Geometry.precision ? (double)a + (value - aa)/(bb - aa) : (double)a;
|
|
|
a62e15 |
}
|
|
|
77c1c7 |
|
|
|
a62e15 |
public double indexByOriginalIndex(double originalIndex)
|
|
|
702257 |
{ return binarySearch(originalIndex, delegate(Point p) { return p.originalIndex; }); }
|
|
|
a62e15 |
public double indexByTime(double time)
|
|
|
702257 |
{ return binarySearch(time, delegate(Point p) { return p.time; }); }
|
|
|
a62e15 |
public double indexByLength(double length)
|
|
|
702257 |
{ return binarySearch(length, delegate(Point p) { return p.length; }); }
|
|
|
a62e15 |
|
|
|
a62e15 |
public double originalIndexByIndex(double index) {
|
|
|
a62e15 |
double frac;
|
|
|
702257 |
Point p0 = floorPoint(index, out frac), p1 = ceilPoint(index);
|
|
|
a62e15 |
return Geometry.interpolationLinear(p0.originalIndex, p1.originalIndex, frac);
|
|
|
a62e15 |
}
|
|
|
a62e15 |
public double timeByIndex(double index) {
|
|
|
a62e15 |
double frac;
|
|
|
702257 |
Point p0 = floorPoint(index, out frac), p1 = ceilPoint(index);
|
|
|
a62e15 |
return Geometry.interpolationLinear(p0.time, p1.time, frac);
|
|
|
a62e15 |
}
|
|
|
a62e15 |
public double lengthByIndex(double index) {
|
|
|
a62e15 |
double frac;
|
|
|
702257 |
Point p0 = floorPoint(index, out frac), p1 = ceilPoint(index);
|
|
|
a62e15 |
return Geometry.interpolationLinear(p0.length, p1.length, frac);
|
|
|
77c1c7 |
}
|
|
|
cfceb0 |
|
|
|
702257 |
public Point calcPoint(double index) {
|
|
|
a62e15 |
return modifier == null
|
|
|
702257 |
? interpolateLinear(index)
|
|
|
702257 |
: modifier.calcPoint( originalIndexByIndex(index) );
|
|
|
a62e15 |
}
|
|
|
a62e15 |
|
|
|
72d2fd |
public Assistance.Point calcTangent(double index, double distance = 0.1) {
|
|
|
72d2fd |
double minDistance = 10.0*Geometry.precision;
|
|
|
72d2fd |
if (distance < minDistance) distance = minDistance;
|
|
|
72d2fd |
Point p = calcPoint(index);
|
|
|
72d2fd |
Point pp = calcPoint(indexByLength(p.length - distance));
|
|
|
72d2fd |
Assistance.Point dp = p.position - pp.position;
|
|
|
72d2fd |
double lenSqr = dp.lenSqr();
|
|
|
72d2fd |
return lenSqr > Geometry.precisionSqr ? dp*Math.Sqrt(1.0/lenSqr) : new Assistance.Point();
|
|
|
72d2fd |
}
|
|
|
72d2fd |
|
|
|
702257 |
public Point interpolateLinear(double index) {
|
|
|
a62e15 |
double frac;
|
|
|
702257 |
Point p0 = floorPoint(index, out frac);
|
|
|
702257 |
Point p1 = ceilPoint(index);
|
|
|
702257 |
return interpolateLinear(p0, p1, frac);
|
|
|
a62e15 |
}
|
|
|
a62e15 |
|
|
|
702257 |
public static Point interpolateLinear(Point p0, Point p1, double l) {
|
|
|
a62e15 |
if (l <= Geometry.precision) return p0;
|
|
|
a62e15 |
if (l >= 1.0 - Geometry.precision) return p1;
|
|
|
702257 |
return new Point(
|
|
|
702257 |
Geometry.interpolationLinear(p0.position, p1.position, l),
|
|
|
702257 |
Geometry.interpolationLinear(p0.pressure, p1.pressure, l),
|
|
|
702257 |
Geometry.interpolationLinear(p0.tilt, p1.tilt, l),
|
|
|
a62e15 |
Geometry.interpolationLinear(p0.originalIndex, p1.originalIndex, l),
|
|
|
a62e15 |
Geometry.interpolationLinear(p0.time, p1.time, l),
|
|
|
702257 |
Geometry.interpolationLinear(p0.length, p1.length, l) );
|
|
|
cfceb0 |
}
|
|
|
7bbf70 |
|
|
|
7bbf70 |
public void print() {
|
|
|
72d2fd |
foreach(Point wp in privatePoints)
|
|
|
7bbf70 |
Console.Write(
|
|
|
7bbf70 |
"{2:f1}, ",
|
|
|
702257 |
wp.position.x,
|
|
|
702257 |
wp.position.y,
|
|
|
7bbf70 |
wp.originalIndex );
|
|
|
7bbf70 |
Console.WriteLine();
|
|
|
7bbf70 |
}
|
|
|
72d2fd |
|
|
|
72d2fd |
public void verify(string message) {
|
|
|
72d2fd |
bool error = false;
|
|
|
72d2fd |
for(int i = 1; i < count; ++i) {
|
|
|
72d2fd |
Point pp = privatePoints[i-1];
|
|
|
72d2fd |
Point p = privatePoints[i];
|
|
|
72d2fd |
if ( Geometry.isGreater(pp.originalIndex, p.originalIndex)
|
|
|
72d2fd |
/*|| Geometry.isGreater(pp.length, p.length)
|
|
|
72d2fd |
|| Geometry.isGreater(pp.time, p.time)
|
|
|
72d2fd |
|| pp.final*/ )
|
|
|
72d2fd |
{
|
|
|
72d2fd |
if (!error) Console.WriteLine("Track error: " + message);
|
|
|
72d2fd |
error = true;
|
|
|
72d2fd |
Console.WriteLine("--- index: " + (i-1) + " / " + i);
|
|
|
72d2fd |
Console.WriteLine(" originalIndex: " + pp.originalIndex + " / " + p.originalIndex);
|
|
|
72d2fd |
Console.WriteLine(" length: " + pp.length + " / " + p.length);
|
|
|
72d2fd |
Console.WriteLine(" time: " + pp.time + " / " + p.time);
|
|
|
72d2fd |
Console.WriteLine(" final: " + pp.final + " / " + p.final);
|
|
|
72d2fd |
}
|
|
|
72d2fd |
}
|
|
|
72d2fd |
if (error) print();
|
|
|
72d2fd |
}
|
|
|
cfceb0 |
}
|
|
|
cfceb0 |
}
|
|
|
cfceb0 |
|