diff --git a/mono/Assistance/Assistance.csproj b/mono/Assistance/Assistance.csproj index 2dedccc..e2127d1 100644 --- a/mono/Assistance/Assistance.csproj +++ b/mono/Assistance/Assistance.csproj @@ -54,11 +54,16 @@ - - + + MainWindow.cs + + + + + diff --git a/mono/Assistance/Geometry.cs b/mono/Assistance/Geometry.cs index f9d248b..e9174cc 100644 --- a/mono/Assistance/Geometry.cs +++ b/mono/Assistance/Geometry.cs @@ -1,13 +1,24 @@ using System; using System.Collections.Generic; +using System.Linq.Expressions; namespace Assistance { public static class Geometry { - public delegate Point TransformFunc(Point p); - public static readonly double precision = 1e-8; + public static readonly double precisionSqr = precision*precision; public static readonly double sqrt2Pi = Math.Sqrt(2.0*Math.PI); + public static bool isEqual(double a, double b) + { return Math.Abs(b - a) <= precision; } + public static bool isGreaterOrEqual(double a, double b) + { return b - a <= precision; } + public static bool isLessOrEqual(double a, double b) + { return isGreaterOrEqual(b, a); } + public static bool isLess(double a, double b) + { return !isGreaterOrEqual(a, b); } + public static bool isGreater(double a, double b) + { return !isGreaterOrEqual(b, a); } + public static double logNormalDistribuitionUnscaled(double x, double x0, double w) { return Math.Exp(-0.5*Math.Pow(Math.Log(x/x0)/w, 2.0))/x; } @@ -32,29 +43,75 @@ namespace Assistance { } } - public static Point noTransform(Point point) { - return point; - } + public static class Interpolation { + public delegate T HalfFunc(T a, double b); + public delegate T FullFunc(T a, T b); + + public static FullFunc add; + public static FullFunc sub; + public static HalfFunc mul; + public static HalfFunc div; - public static Point transform(List funcs, Point point) { - Point p = point; - foreach(TransformFunc func in funcs) - p = func(p); - return p; + static Interpolation() { + { // add + ParameterExpression a = Expression.Parameter(typeof(T)); + ParameterExpression b = Expression.Parameter(typeof(T)); + add = Expression.Lambda(Expression.Add(a, b), a, b).Compile(); + } + { // sub + ParameterExpression a = Expression.Parameter(typeof(T)); + ParameterExpression b = Expression.Parameter(typeof(T)); + sub = Expression.Lambda(Expression.Subtract(a, b), a, b).Compile(); + } + { // mul + ParameterExpression a = Expression.Parameter(typeof(T)); + ParameterExpression b = Expression.Parameter(typeof(double)); + mul = Expression.Lambda(Expression.Multiply(a, b), a, b).Compile(); + } + { // div + ParameterExpression a = Expression.Parameter(typeof(T)); + ParameterExpression b = Expression.Parameter(typeof(double)); + div = Expression.Lambda(Expression.Divide(a, b), a, b).Compile(); + } + } + + public static T linear(T p0, T p1, double l) + { return add(mul(sub(p1, p0), l), p0); } + + public static double spline(T p0, T p1, T t0, T t1, double l) { + double ll = l*l; + double lll = ll*l; + return add( add( mul(p0, ( 2.0*lll - 3.0*ll + 1.0)), + mul(p1, (-2.0*lll + 3.0*ll )) ), + add( mul(t0, ( lll - 2.0*ll + l )), + mul(t1, ( lll - 1.0*ll )) )); + } + + public static double splineTangent(T p0, T p1, T t0, T t1, double l) { + double ll = l*l; + return add( mul(sub(p0, p1), 6.0*(ll - l)), + add( mul(t0, ( 3.0*ll - 4.0*l + 1.0)), + mul(t1, ( 3.0*ll - 2.0*l )) )); + } } + + public static double interpolationLinear(double p0, double p1, double l) + { return (p1 - p0)*l + p0; } - public static double splinePoint(double p0, double p1, double t0, double t1, double l) { - return p0*(( 2.0*l - 3.0)*l*l + 1.0) - + p1*((-2.0*l + 3.0)*l*l ) - + t0*(( l - 2.0)*l*l + l ) - + t1*(( l - 1.0)*l*l ); + public static double interpolationSpline(double p0, double p1, double t0, double t1, double l) { + double ll = l*l; + double lll = ll*l; + return p0*( 2.0*lll - 3.0*ll + 1.0) + + p1*(-2.0*lll + 3.0*ll ) + + t0*( lll - 2.0*ll + l ) + + t1*( lll - 1.0*ll ); } - public static Point splinePoint(Point p0, Point p1, Point t0, Point t1, double l) { - return p0*(( 2.0*l - 3.0)*l*l + 1.0) - + p1*((-2.0*l + 3.0)*l*l ) - + t0*(( l - 2.0)*l*l + l ) - + t1*(( l - 1.0)*l*l ); + public static double interpolationSplineTangent(double p0, double p1, double t0, double t1, double l) { + double ll = l*l; + return (p0 - p1)*6.0*(ll - l) + + t0*( 3.0*ll - 4.0*l + 1.0) + + t1*( 3.0*ll - 2.0*l ); } } } diff --git a/mono/Assistance/KeyHistory.cs b/mono/Assistance/KeyHistory.cs index ea3fdaf..140e9cc 100644 --- a/mono/Assistance/KeyHistory.cs +++ b/mono/Assistance/KeyHistory.cs @@ -18,6 +18,12 @@ namespace Assistance { heldTicks = history.hold(ticks); } + public Holder offset(double timeOffset) { + return Geometry.isEqual(timeOffset, 0.0) + ? this + : new Holder(history, ticks, this.timeOffset + timeOffset); + } + public KeyState.Holder get(double time) { long dticks = (long)Math.Ceiling(Timer.frequency*(time + timeOffset)); KeyState state = history.get(ticks + dticks); @@ -75,7 +81,7 @@ namespace Assistance { } private long hold(long ticks) { - long heldTicks = Math.Max(ticks, current.ticks); + long heldTicks = Math.Max(ticks, states[0].ticks); locks.Insert(findLock(heldTicks) + 1, heldTicks); return heldTicks; } diff --git a/mono/Assistance/Point.cs b/mono/Assistance/Point.cs index 7814c8b..d3f4f7d 100644 --- a/mono/Assistance/Point.cs +++ b/mono/Assistance/Point.cs @@ -24,7 +24,7 @@ namespace Assistance { { return a.x*b.x + a.y*b.y; } public bool isEqual(Point other) - { return (this - other).lenSqr() <= Geometry.precision*Geometry.precision; } + { return (this - other).lenSqr() <= Geometry.precisionSqr; } public double lenSqr() { return x*x + y*y; } public double len() @@ -32,7 +32,7 @@ namespace Assistance { public Point normalize() { double l = len(); - return l > Geometry.precision*Geometry.precision ? this/l : this; + return l > Geometry.precision ? this/l : this; } public Point rotate(double angle) { diff --git a/mono/Assistance/Tool.cs b/mono/Assistance/Tool.cs index a89a607..d0cc2ca 100644 --- a/mono/Assistance/Tool.cs +++ b/mono/Assistance/Tool.cs @@ -9,8 +9,10 @@ namespace Assistance { Multiline = 4 }; - - public class InputHandler { + public class Tool { + public Modifiers getAvailableModifiers() + { return Modifiers.None; } + public void activate() { } public void keyPress(Gdk.Key key, InputState state) { } @@ -25,46 +27,53 @@ namespace Assistance { public bool paintApply() { return false; } public void paintCancel() { } - public void disactivate() { } - } + public void draw(Cairo.Context context) { } - - public class InputModifier { - public InputHandler getNext() { return null; } - - public void activate() - { if (getNext() != null) getNext().activate(); } - - public void keyPress(Gdk.Key key, InputState state) - { if (getNext() != null) getNext().keyPress(key, state); } - public void keyRelease(Gdk.Key key, InputState state) - { if (getNext() != null) getNext().keyRelease(key, state); } - public void buttonPress(Gdk.Device device, uint button, InputState state) - { if (getNext() != null) getNext().buttonPress(device, button, state); } - public void buttonRelease(Gdk.Device device, uint button, InputState state) - { if (getNext() != null) getNext().buttonRelease(device, button, state); } - - public bool paintBegin() - { return getNext() == null ? false : getNext().paintBegin(); } - public void paintTrackBegin(Track track) - { if (getNext() != null) getNext().paintTrackBegin(track); } - public void paintTrackPoint(Track track) - { if (getNext() != null) getNext().paintTrackPoint(track); } - public void paintTrackEnd(Track track) - { if (getNext() != null) getNext().paintTrackEnd(track); } - public bool paintApply() - { return getNext() == null ? false : getNext().paintApply(); } - public void paintCancel() - { if (getNext() != null) getNext().paintCancel(); } - - public void disactivate() - { if (getNext() != null) getNext().disactivate(); } - } - - - public class Tool: InputHandler { - public Modifiers getAvailableModifiers() - { return Modifiers.None; } + public void deactivate() { } } } +/* +TODO: + ////////////////////////////////////////// + // deprecated + ////////////////////////////////////////// + + public static readonly Pen pen = new Pen("Dark Green", 3.0); + public static readonly Pen penSpecial = new Pen("Blue", 3.0); + public static readonly Pen penPreview = new Pen("Dark Green", 1.0, 0.25); + + public void draw(Cairo.Context context, bool preview = false) { + if (preview) { + if (points.Count < 2) + return; + context.Save(); + penPreview.apply(context); + context.MoveTo(points[0].point.x, points[0].point.y); + for(int i = 1; i < points.Count; ++i) + context.LineTo(points[i].point.x, points[i].point.y); + context.Stroke(); + context.Restore(); + } else { + context.Save(); + pen.apply(context); + foreach(TrackPoint p in points) { + double t = p.keyState.howLongPressed(Gdk.Key.m) + + p.buttonState.howLongPressed(3); + double w = p.pressure*pen.width + 5.0*t; + context.Arc(p.point.x, p.point.y, 2.0*w, 0.0, 2.0*Math.PI); + context.Fill(); + } + context.Restore(); + } + } + + public Rectangle getBounds() { + if (points.Count == 0) + return new Rectangle(); + Rectangle bounds = new Rectangle(points[0].point); + foreach(TrackPoint p in points) + bounds = bounds.expand(p.point); + return bounds.inflate(Math.Max(pen.width, penPreview.width) + 2.0); + } +*/ \ No newline at end of file diff --git a/mono/Assistance/Track.cs b/mono/Assistance/Track.cs index 8e6cc0c..66cf7b0 100644 --- a/mono/Assistance/Track.cs +++ b/mono/Assistance/Track.cs @@ -4,143 +4,256 @@ using Assistance.Drawing; namespace Assistance { public class Track { - public static readonly Pen pen = new Pen("Dark Green", 3.0); - public static readonly Pen penSpecial = new Pen("Blue", 3.0); - public static readonly Pen penPreview = new Pen("Dark Green", 1.0, 0.25); + public class Owner { } + + public class Handler { + public readonly Owner owner; + public readonly Track original; + public readonly List tracks = new List(); + public Handler(Owner owner, Track original) { + this.owner = owner; + this.original = original; + } + } + + public class Modifier { + public readonly Handler handler; + public readonly double timeOffset; + + public Modifier(Handler handler, double timeOffset = 0.0) + { this.handler = handler; this.timeOffset = timeOffset; } + public Owner owner + { get { return handler.owner; } } + public Track original + { get { return handler.original; } } + public TrackPoint calcWayPoint(double originalIndex) + { return original.calcWayPoint(originalIndex); } + } + + public struct Point { + public Assistance.Point position; + public double pressure; + public Point tilt; + + public Point( + Assistance.Point position, + double pressure, + Point tilt + ) { + this.position = position; + this.pressure = pressure; + this.tilt = tilt; + } + + public static Point operator+ (Point a, Point b) + { return new Point(a.position + b.position, a.pressure + b.pressure, a.tilt + b.tilt); } + public static Point operator- (Point a, Point b) + { return new Point(a.position - b.position, a.pressure - b.pressure, a.tilt - b.tilt); } + public static Point operator* (Point a, double b) + { return new Point(a.position*b, a.pressure*b, a.tilt*b); } + public static Point operator* (double b, Point a) + { return a*b; } + public static Point operator/ (Point a, double b) + { return this*(1.0/b); } + + public bool isEqual(Point other) { + return position.isEqual(other.position) + && Geometry.isEqual(pressure, other.pressure) + && tilt.isEqual(other.tilt); + } + + public Point normalize() { + double l = position.len(); + return l > Geometry.precision ? this/l : this; + } + } + + public struct WayPoint { + public Point point; + public Point tangent; + + public double originalIndex; + public double time; + public double length; + + public bool final; + + public WayPoint( + Point point, + Point tangent, + double originalIndex = 0.0, + double time = 0.0, + double length = 0.0, + bool final = false + ) { + this.point = point; + this.tangent = tangent; + this.originalIndex = originalIndex; + this.time = time; + this.length = length; + this.final = final; + } + } + private static long lastTouchId; - public long touchId; + public readonly long touchId; public readonly Gdk.Device device; - public readonly List points = new List(); + public readonly List points = new List(); + public readonly KeyHistory.Holder keyHistory; + public readonly KeyHistory.Holder buttonHistory; - private readonly List parents = new List(); - private readonly List transformFuncs = new List(); + public readonly Modifier modifier; + + public Handler handler; + public int wayPointsRemoved; + public int wayPointsAdded; - public static long getTouchId() { return ++lastTouchId; } + public static long genTouchId() { return ++lastTouchId; } - public Track(long touchId, Gdk.Device device) - { - this.touchId = touchId; + public Track( + Gdk.Device device, + KeyHistory.Holder keyHistory, + KeyHistory.Holder buttonHistory, + long touchId + ) { this.device = device; + this.keyHistory = keyHistory; + this.buttonHistory = buttonHistory; + this.touchId = touchId; } + + public Track( + Gdk.Device device = null, + KeyHistory.Holder keyHistory = null, + KeyHistory.Holder buttonHistory = null + ): + this(device, genTouchId()) { } - public Track(Track parent, Geometry.TransformFunc transformFunc): - this(parent.touchId, parent.device) - { - parents.AddRange(parent.parents); - parents.Add(parent); - transformFuncs.AddRange(parent.transformFuncs); - transformFuncs.Add(transformFunc); - } + public Track(Modifier modifier, long touchId): + this( modifier.original.device, + modifier.original.keyHistory.offset( modifier.timeOffset ), + modifier.original.buttonHistory.offset( modifier.timeOffset ), + touchId ) + { this.modifier = modifier; } - public Track(Track parent, Geometry.TransformFunc transformFunc, double precision): - this(parent, transformFunc) - { - rebuild(precision); - } + public Track(Modifier modifier): + this(modifier, genTouchId()) { } - public Track createChild(Geometry.TransformFunc transformFunc){ - return new Track(this, transformFunc); - } + public Track original + { get { return modifier != null ? modifier.original : null; } } + public double timeOffset + { get { return modifier != null ? modifier.timeOffset : 0.0; } } - public Track createChildAndBuild(Geometry.TransformFunc transformFunc, double precision = 1.0) { - return new Track(this, transformFunc, precision); - } + public Track getRoot() + { return original == null ? this : original.getRoot(); } + public int getLevel() + { return original == null ? 0 : original.getLevel() + 1; } - public Rectangle getBounds() { - if (points.Count == 0) - return new Rectangle(); - Rectangle bounds = new Rectangle(points[0].point); - foreach(TrackPoint p in points) - bounds = bounds.expand(p.point); - return bounds.inflate(Math.Max(pen.width, penPreview.width) + 2.0); - } - - public TrackPoint transform(TrackPoint p) { - p.point = Geometry.transform(transformFuncs, p.point); - return p; + public WayPoint getFirst() + { return getWayPoint(0); } + public WayPoint getLast() + { return getWayPoint(points.Count - 1); } + public bool isFinished() + { return getLast().final; } + + public int clampIndex(int index) + { return Math.Min(Math.Max(index, 0), points.Count - 1); } + public int floorIndex(double index, out double frac) { + int i = (int)Math.Floor(index + Geometry.precision); + if (i > points.Count - 1) + { frac = 0.0; return points.Count - 1; } + if (i < 0) + { frac = 0.0; return 0; } + frac = Math.Max(0.0, index - (double)i); + return i; } - - private void addSpline( - TrackPoint p0, TrackPoint p1, - TrackPoint t0, TrackPoint t1, - TrackPoint tp0, TrackPoint tp1, - double l0, double l1, - double precisionSqr - ) { - if ((tp1.point - tp0.point).lenSqr() < precisionSqr) { - points.Add(tp1); - } else { - double l = 0.5*(l0 + l1); - TrackPoint p = p0.spawn( - Geometry.splinePoint(p0.point, p1.point, t0.point, t1.point, l), - p0.time + l*(p1.time - p0.time), - Geometry.splinePoint(p0.pressure, p1.pressure, t0.pressure, t1.pressure, l), - Geometry.splinePoint(p0.tilt, p1.tilt, t0.tilt, t1.tilt, l) ); - TrackPoint tp = transform(p); - addSpline(p0, p1, t0, t1, tp0, tp, l0, l, precisionSqr); - addSpline(p0, p1, t0, t1, tp, tp1, l, l1, precisionSqr); - } + public int floorIndex(double index) + { return clampIndex((int)Math.Floor(index + Geometry.precision)); } + public int ceilIndex(double index) + { return clampIndex((int)Math.Floor(index + Geometry.precision)); } + + public WayPoint getWayPoint(int index) { + index = clampIndex(index); + return index < 0 ? new WayPoint() : points[index]; } + public WayPoint floorWayPoint(double index, out double frac) + { return getWayPoint(floorIndex(index, out frac)); } + public WayPoint floorWayPoint(double index) + { return getWayPoint(floorIndex(index)); } + public WayPoint ceilWayPoint(double index) + { return getWayPoint(floorIndex(index)); } - public void rebuild(double precision = 1.0) { - if (parents.Count == 0) return; + private delegate double WayPointFieldGetter(WayPoint p); + private double binarySearch(double value, WayPointFieldGetter getter) { + // points[a].value <= value < points[b].value - points.Clear(); + if (points.Count <= 0) return 0.0; + int a = 0; + double aa = getter(points[a]); + if (Geometry.isLess(aa, value)) return 0.0; + + int b = points.Count - 1; + double bb = getter(points[b]); + if (Geometry.isGreaterOrEqual(value, bb)) return (double)b; - Track root = parents[0]; - if (root.points.Count < 2) { - foreach(TrackPoint p in root.points) - points.Add( transform(p) ); - return; + while(true) { + int c = (a + b)/2; + if (a == c) break; + double cc = getter(points[c]); + if (Geometry.isLess(value, cc)) + { b = c; bb = cc; } else { a = c; aa = cc; } } + + return Geometry.isLess(aa, bb) ? value + (value - aa)/(bb - aa) : value; + } - double precisionSqr = precision * precision; - TrackPoint p0 = root.points[0]; - TrackPoint p1 = root.points[1]; - TrackPoint t0 = new TrackPoint(); - TrackPoint tp0 = transform(p0); - points.Add(tp0); - for(int i = 1; i < root.points.Count; ++i) { - TrackPoint p2 = root.points[i+1 < root.points.Count ? i+1 : i]; - TrackPoint tp1 = transform(p1); - double dt = p2.time - p0.time; - TrackPoint t1 = dt > Geometry.precision - ? (p2 - p0)*(p1.time - p0.time)/dt - : new TrackPoint(); - addSpline(p0, p1, t0, t1, tp0, tp1, 0.0, 1.0, precisionSqr); - - p0 = p1; - p1 = p2; - tp0 = tp1; - t0 = t1; - } + public double indexByOriginalIndex(double originalIndex) + { return binarySearch(originalIndex, delegate(WayPoint p) { return p.originalIndex; }); } + public double indexByTime(double time) + { return binarySearch(time, delegate(WayPoint p) { return p.time; }); } + public double indexByLength(double length) + { return binarySearch(length, delegate(WayPoint p) { return p.length; }); } + + public double originalIndexByIndex(double index) { + double frac; + WayPoint p0 = floorWayPoint(index, out frac), p1 = ceilWayPoint(index); + return Geometry.interpolationLinear(p0.originalIndex, p1.originalIndex, frac); + } + public double timeByIndex(double index) { + double frac; + WayPoint p0 = floorWayPoint(index, out frac), p1 = ceilWayPoint(index); + return Geometry.interpolationLinear(p0.time, p1.time, frac); + } + public double lengthByIndex(double index) { + double frac; + WayPoint p0 = floorWayPoint(index, out frac), p1 = ceilWayPoint(index); + return Geometry.interpolationLinear(p0.length, p1.length, frac); } - public void draw(Cairo.Context context, bool preview = false) { - if (preview) { - if (points.Count < 2) - return; - context.Save(); - penPreview.apply(context); - context.MoveTo(points[0].point.x, points[0].point.y); - for(int i = 1; i < points.Count; ++i) - context.LineTo(points[i].point.x, points[i].point.y); - context.Stroke(); - context.Restore(); - } else { - context.Save(); - pen.apply(context); - foreach(TrackPoint p in points) { - double t = p.keyState.howLongPressed(Gdk.Key.m) - + p.buttonState.howLongPressed(3); - double w = p.pressure*pen.width + 5.0*t; - context.Arc(p.point.x, p.point.y, 2.0*w, 0.0, 2.0*Math.PI); - context.Fill(); - } - context.Restore(); - } + public WayPoint calcWayPoint(double index) { + return modifier == null + ? interpolate(index) + : modifier.calcWayPoint( originalIndexByIndex(index) ); + } + + public WayPoint interpolate(double index) { + double frac; + WayPoint p0 = floorIndex(index, out frac); + WayPoint p1 = ceilWayPoint(index); + return interpolate(p0, p1, frac); + } + + public static WayPoint interpolate(WayPoint p0, WayPoint p1, double l) { + if (l <= Geometry.precision) return p0; + if (l >= 1.0 - Geometry.precision) return p1; + return new WayPoint( + Geometry.Interpolation.spline(p0.point, p1.point, p0.tangent, p1.tangent, l), + Geometry.Interpolation.splineTangent(p0.point, p1.point, p0.tangent, p1.tangent, l), + Geometry.interpolationLinear(p0.originalIndex, p1.originalIndex, l), + Geometry.interpolationLinear(p0.time, p1.time, l), + Geometry.interpolationLinear(p0.length, p1.length, l) ); } } } diff --git a/mono/Assistance/TrackPoint.cs b/mono/Assistance/TrackPoint.cs deleted file mode 100644 index adb1b7e..0000000 --- a/mono/Assistance/TrackPoint.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; - -namespace Assistance { - public struct TrackPoint { - public Point point; - public double time; - public double pressure; - public Point tilt; - - public KeyState.Holder keyState; - public KeyState.Holder buttonState; - - public TrackPoint spawn(Point point, double time, double pressure, Point tilt) { - TrackPoint p = this; - p.point = point; - p.time = time; - p.pressure = pressure; - p.tilt = tilt; - p.keyState.timeOffset += time - this.time; - p.buttonState.timeOffset += time - this.time; - return p; - } - - public static TrackPoint operator+ (TrackPoint a, TrackPoint b) - { return a.spawn(a.point + b.point, a.time + b.time, a.pressure + b.pressure, a.tilt + b.tilt); } - public static TrackPoint operator- (TrackPoint a, TrackPoint b) - { return a.spawn(a.point - b.point, a.time - b.time, a.pressure - b.pressure, a.tilt - b.tilt); } - public static TrackPoint operator* (TrackPoint a, double b) - { return a.spawn(a.point*b, a.time*b, a.pressure*b, a.tilt*b); } - public static TrackPoint operator* (double b, TrackPoint a) - { return a*b; } - public static TrackPoint operator/ (TrackPoint a, double b) - { return a.spawn(a.point/b, a.time/b, a.pressure/b, a.tilt/b); } - } -} -