1 /* Invisible Vector Library
2 * coded by Ketmar // Invisible Vector <ketmar@ketmar.no-ip.org>
3 * Understanding is not required. Only obedience.
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 // Protractor gesture recognizer, v0
19 module iv
.gengpro0
/*is aliced*/;
23 // ////////////////////////////////////////////////////////////////////////// //
27 // ////////////////////////////////////////////////////////////////////////// //
28 public alias GengFloat
= float;
31 // ////////////////////////////////////////////////////////////////////////// //
32 public enum MinGestureMatch
= 1.5;
35 // ////////////////////////////////////////////////////////////////////////// //
37 enum NormalizedPoints
= 16; // the paper says that this is enough for protractor to work ok
38 alias GengPatternPoints
= GengFloat
[NormalizedPoints
*2];
41 // ////////////////////////////////////////////////////////////////////////// //
42 enum MinPointDistance
= 4;
45 // ////////////////////////////////////////////////////////////////////////// //
46 // ignore possible overflows here
47 import std
.traits
: isFloatingPoint
;
49 GengFloat
distance(FP
) (in FP x0
, in FP y0
, in FP x1
, in FP y1
) if (isFloatingPoint
!FP
) {
50 import std
.math
: sqrt
;
53 return sqrt(dx
*dx
+dy
*dy
);
57 // ////////////////////////////////////////////////////////////////////////// //
58 public class PTGlyph
{
60 GengPatternPoints patpoints
;
61 GengFloat
[] points
; // [0]:x, [1]:y, [2]:x, [3]:y, etc...
62 bool mNormalized
; // true: `patpoints` is ok
63 bool mOriented
= true;
66 private static normBlkAttr (void* ptr
) {
67 import core
.memory
: GC
;
69 if (ptr
!is null && ptr
is GC
.addrOf(ptr
)) GC
.setAttr(ptr
, GC
.BlkAttr
.NO_INTERIOR
);
73 this () nothrow @safe @nogc {}
74 this (string aname
, bool aoriented
=true) nothrow @safe @nogc { mName
= aname
; mOriented
= aoriented
; }
75 this (string aname
, in GengPatternPoints apat
, bool aoriented
) nothrow @safe @nogc {
79 mOriented
= aoriented
;
83 @property bool valid () const pure nothrow @safe @nogc { pragma(inline
, true); return (mNormalized || points
.length
>= 4); }
84 @property bool normalized () const pure nothrow @safe @nogc { pragma(inline
, true); return mNormalized
; }
85 @property bool oriented () const pure nothrow @safe @nogc { pragma(inline
, true); return mOriented
; }
86 @property void oriented (bool v
) pure nothrow @safe @nogc { pragma(inline
, true); if (!mNormalized
) mOriented
= v
; } // can't be changed for normalized glyphs
88 @property string
name () const pure nothrow @safe @nogc { pragma(inline
, true); return mName
; }
89 @property void name (string v
) @safe nothrow @nogc { pragma(inline
, true); mName
= v
; }
91 usize
length () const pure nothrow @safe @nogc { pragma(inline
, true); return (mNormalized ? NormalizedPoints
: points
.length
/2); }
92 alias opDollar
= length
;
94 auto x (usize idx
) const pure nothrow @safe @nogc {
97 return (idx
*2 < points
.length ? points
[idx
*2] : typeof(points
[0]).nan
);
99 return (idx
< NormalizedPoints ? patpoints
[idx
*2] : typeof(points
[0]).nan
);
103 auto y (usize idx
) const pure nothrow @safe @nogc {
104 pragma(inline
, true);
106 return (idx
*2 < points
.length ? points
[idx
*2+1] : typeof(points
[0]).nan
);
108 return (idx
< NormalizedPoints ? patpoints
[idx
*2+1] : typeof(points
[0]).nan
);
112 auto clear () nothrow {
113 if (points
.length
) { points
.length
= 0; points
.assumeSafeAppend
; }
119 auto clone () const {
120 auto res
= new PTGlyph(mName
);
121 res
.mNormalized
= mNormalized
;
122 res
.mOriented
= mOriented
;
123 res
.patpoints
[] = patpoints
[];
124 if (points
.length
> 0) {
125 res
.points
= points
.dup
;
126 normBlkAttr(res
.points
.ptr
);
131 auto addPoint (int x
, int y
) {
132 if (mNormalized
) throw new Exception("can't add point to normalized glyph");
133 if (points
.length
> 0) {
134 // check distance and don't add points that are too close to each other
135 immutable lx
= x
-points
[$-2], ly
= y
-points
[$-1];
136 if (lx
*lx
+ly
*ly
< MinPointDistance
*MinPointDistance
) return this;
138 auto optr
= points
.ptr
;
140 if (optr
!= points
.ptr
) { normBlkAttr(points
.ptr
); optr
= points
.ptr
; }
142 if (optr
!= points
.ptr
) { normBlkAttr(points
.ptr
); optr
= points
.ptr
; }
147 auto normalize (bool dropPoints
=true) {
149 if (points
.length
< 4) throw new Exception("glyph must have at least two points");
150 buildNormPoints(patpoints
, points
, mOriented
);
154 points
.assumeSafeAppend
;
161 GengFloat
match (const(PTGlyph
) sample
) const pure nothrow @safe @nogc {
162 if (sample
is null ||
!sample
.valid ||
!valid
) return -GengFloat
.infinity
;
164 // this is normalized
165 return match(patpoints
, sample
);
167 // this is not normalized
168 GengPatternPoints v1
= void;
169 buildNormPoints(v1
, points
, mOriented
);
170 return match(v1
, sample
);
174 string
patpointsToString () {
175 import std
.string
: format
;
176 return format("%s", patpoints
);
181 static GengFloat
match (in GengPatternPoints tpl
, const(PTGlyph
) sample
) pure nothrow @safe @nogc {
182 if (sample
is null ||
!sample
.valid
) return -GengFloat
.infinity
;
183 if (sample
.mNormalized
) {
184 return match(tpl
, sample
.patpoints
);
186 GengPatternPoints spts
= void;
187 buildNormPoints(spts
, sample
.points
, sample
.mOriented
);
188 return match(tpl
, spts
);
192 static GengFloat
match (in GengPatternPoints v0
, in GengPatternPoints v1
) pure nothrow @safe @nogc {
193 return 1.0/optimalCosineDistance(v0
, v1
);
196 static GengFloat
optimalCosineDistance (in GengPatternPoints v0
, in GengPatternPoints v1
) pure nothrow @safe @nogc {
197 import std
.math
: atan
, acos
, cos
, sin
;
198 GengFloat a
= 0.0, b
= 0.0;
199 foreach (immutable idx
; 0..NormalizedPoints
) {
200 a
+= v0
[idx
*2+0]*v1
[idx
*2+0]+v0
[idx
*2+1]*v1
[idx
*2+1];
201 b
+= v0
[idx
*2+0]*v1
[idx
*2+1]-v0
[idx
*2+1]*v1
[idx
*2+0];
203 immutable GengFloat angle
= atan(b
/a
);
204 return acos(a
*cos(angle
)+b
*sin(angle
));
207 // glyph length (not point counter!)
208 static GengFloat
glyphLength (in GengFloat
[] points
) pure nothrow @safe @nogc {
210 if (points
.length
>= 4) {
211 // don't want to bring std.algo here
212 GengFloat px
= points
[0], py
= points
[1];
213 foreach (immutable idx
; 2..points
.length
/2) {
214 immutable cx
= points
[idx
*2+0], cy
= points
[idx
*2+1];
215 res
+= distance(px
, py
, cx
, cy
);
223 static void resample (out GengPatternPoints ptres
, in GengFloat
[] points
) pure @safe nothrow @nogc {
224 assert(points
.length
>= 4);
225 immutable GengFloat I
= glyphLength(points
)/(NormalizedPoints
-1); // interval length
227 GengFloat prx
= points
[0];
228 GengFloat pry
= points
[1];
229 // add first point as-is
232 usize ptpos
= 2, oppos
= 2;
233 while (oppos
< points
.length
) {
234 immutable GengFloat cx
= points
[oppos
], cy
= points
[oppos
+1];
235 immutable d
= distance(prx
, pry
, cx
, cy
);
237 immutable dd = (I
-D
)/d
;
238 immutable qx
= prx
+dd*(cx
-prx
);
239 immutable qy
= pry
+dd*(cy
-pry
);
240 assert(ptpos
< NormalizedPoints
*2);
243 // use 'q' as previous point
254 // somtimes we fall a rounding-error short of adding the last point, so add it if so
255 if (ptpos
/2 == NormalizedPoints
-1) {
256 ptres
[ptpos
++] = points
[$-2];
257 ptres
[ptpos
++] = points
[$-1];
259 assert(ptpos
== NormalizedPoints
*2);
262 // stroke is not required to be centered, but it must be resampled
263 static void vectorize (out GengPatternPoints vres
, in GengPatternPoints ptx
, bool orientationSensitive
)
264 pure nothrow @safe @nogc
266 import std
.math
: atan2
, cos
, sin
, floor
, sqrt
, PI
;
267 GengPatternPoints pts
;
268 GengFloat indAngle
, delta
;
269 GengFloat cx
= 0.0, cy
= 0.0;
271 foreach (immutable idx
; 0..NormalizedPoints
) {
275 cx
/= NormalizedPoints
;
276 cy
/= NormalizedPoints
;
277 foreach (immutable idx
; 0..NormalizedPoints
) {
278 pts
[idx
*2+0] = ptx
[idx
*2+0]-cx
;
279 pts
[idx
*2+1] = ptx
[idx
*2+1]-cy
;
281 indAngle
= atan2(pts
[1], pts
[0]); // always must be done for centered stroke
282 if (orientationSensitive
) {
283 immutable base_orientation
= (PI
/4.0)*floor((indAngle
+PI
/8.0)/(PI
/4.0));
284 delta
= base_orientation
-indAngle
;
288 immutable GengFloat cosd
= cos(delta
);
289 immutable GengFloat sind
= sin(delta
);
291 foreach (immutable idx
; 0..NormalizedPoints
) {
292 immutable nx
= pts
[idx
*2+0]*cosd
-pts
[idx
*2+1]*sind
;
293 immutable ny
= pts
[idx
*2+1]*cosd
+pts
[idx
*2+0]*sind
;
298 immutable GengFloat magnitude
= sqrt(sum
);
299 foreach (immutable idx
; 0..NormalizedPoints
*2) vres
[idx
] /= magnitude
;
302 static void buildNormPoints (out GengPatternPoints vres
, in GengFloat
[] points
, bool orientationSensitive
)
303 pure nothrow @safe @nogc
305 assert(points
.length
>= 4);
306 GengPatternPoints tmp
= void;
307 resample(tmp
, points
);
308 vectorize(vres
, tmp
, orientationSensitive
);
312 PTGlyph
findMatch(R
) (auto ref R grng
, GengFloat
* outscore
=null) const
313 if (isInputRange
!R
&& !isInfinite
!R
&& is(ElementType
!R
: PTGlyph
))
315 GengFloat bestScore
= -GengFloat
.infinity
;
317 if (outscore
!is null) *outscore
= GengFloat
.nan
;
319 // build normalized `this` glyph in pts
320 GengPatternPoints pts
= void;
321 if (this.mNormalized
) {
322 pts
[] = this.patpoints
[];
324 buildNormPoints(pts
, points
, mOriented
);
326 while (!grng
.empty
) {
327 auto gs
= grng
.front
;
329 if (gs
is null ||
!gs
.valid
) continue;
330 GengFloat score
= match(pts
, gs
);
331 if (score
>= MinGestureMatch
&& score
> bestScore
) {
337 if (res
!is null && outscore
!is null) *outscore
= bestScore
;
343 // ////////////////////////////////////////////////////////////////////////// //
347 public void gstLibLoad(R
) (auto ref R orng
, VFile st
) if (isOutputRange
!(R
, PTGlyph
)) {
348 auto sign
= st
.readNum
!ulong();
349 if (sign
!= 0x304244525450384BUL
&& sign
!= 0x314244525450384BUL
) throw new Exception("invalid library signature"); // "K8PTRDB0"/1
350 ubyte ver
= ((sign
>>56)-0x30)&0xff;
351 auto cnt
= st
.readNum
!uint();
352 if (cnt
> 0x7fff_ffff
) throw new Exception("too many glyphs");
353 if (cnt
== 0) return;
354 foreach (immutable idx
; 0..cnt
) {
356 auto len
= st
.readNum
!uint();
357 if (len
> 1024) throw new Exception("glyph name too long");
360 auto buf
= new char[](len
);
361 st
.rawReadExact(buf
);
362 name
= cast(string
)buf
; // it is safe to cast here
365 GengPatternPoints pts
= void;
366 foreach (ref pt
; pts
) {
367 pt
= st
.readNum
!float();
368 if (pt
!= pt
) throw new Exception("invalid number"); // nan check
370 bool oriented
= true;
371 if (ver
== 1) oriented
= (st
.readNum
!ubyte != 0);
372 auto g
= new PTGlyph(name
, pts
, oriented
);
378 public PTGlyph
[] gstLibLoad (VFile fl
) {
381 void put (PTGlyph g
) nothrow { if (g
!is null && g
.valid
) gls
~= g
; }
385 return (r
.gls
.length ? r
.gls
: null);
389 // ////////////////////////////////////////////////////////////////////////// //
390 public void gstLibSave(R
) (VFile st
, auto ref R grng
) if (isInputRange
!R
&& !isInfinite
!R
&& is(ElementType
!R
: PTGlyph
)) {
391 st
.writeNum
!ulong(0x314244525450384BuL
); // "K8PTRDB1"
392 static if (hasLength
!R
) {
393 auto cnt
= grng
.length
;
394 if (cnt
> 0x7fff_ffff
) throw new Exception("too many glyphs");
395 st
.writeNum
!uint(cast(uint)cnt
);
398 auto cntpos
= st
.tell
;
401 while (!grng
.empty
) {
404 if (g
is null ||
!g
.valid
) throw new Exception("can't save invalid glyph");
406 if (g
.name
.length
> 1024) throw new Exception("glyph name too long");
407 st
.writeNum
!uint(cast(uint)g
.name
.length
);
408 st
.rawWriteExact(g
.name
);
410 GengPatternPoints pts
= void;
412 pts
[] = g
.patpoints
[];
414 g
.buildNormPoints(pts
, g
.points
, g
.mOriented
);
416 foreach (immutable pt
; pts
) st
.writeNum
!float(cast(float)pt
);
417 st
.writeNum
!ubyte(g
.mOriented ?
1 : 0);
418 static if (!hasLength
!R
) {
419 if (cnt
== 0x7fff_ffff
) throw new Exception("too many glyphs");
423 static if (!hasLength
!R
) {
426 st
.writeNum
!uint(cast(uint)cnt
);