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, version 3 of the License ONLY.
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 // Protractor gesture recognizer, v0
18 module iv
.gengpro0
/*is aliced*/;
22 // ////////////////////////////////////////////////////////////////////////// //
26 // ////////////////////////////////////////////////////////////////////////// //
27 public alias GengFloat
= float;
30 // ////////////////////////////////////////////////////////////////////////// //
31 public enum MinGestureMatch
= 1.5;
34 // ////////////////////////////////////////////////////////////////////////// //
36 enum NormalizedPoints
= 16; // the paper says that this is enough for protractor to work ok
37 alias GengPatternPoints
= GengFloat
[NormalizedPoints
*2];
40 // ////////////////////////////////////////////////////////////////////////// //
41 enum MinPointDistance
= 4;
44 // ////////////////////////////////////////////////////////////////////////// //
45 // ignore possible overflows here
46 import std
.traits
: isFloatingPoint
;
48 GengFloat
distance(FP
) (in FP x0
, in FP y0
, in FP x1
, in FP y1
) if (isFloatingPoint
!FP
) {
49 import std
.math
: sqrt
;
52 return sqrt(dx
*dx
+dy
*dy
);
56 // ////////////////////////////////////////////////////////////////////////// //
57 public class PTGlyph
{
59 GengPatternPoints patpoints
;
60 GengFloat
[] points
; // [0]:x, [1]:y, [2]:x, [3]:y, etc...
61 bool mNormalized
; // true: `patpoints` is ok
62 bool mOriented
= true;
65 private static normBlkAttr (void* ptr
) {
66 import core
.memory
: GC
;
68 if (ptr
!is null && ptr
is GC
.addrOf(ptr
)) GC
.setAttr(ptr
, GC
.BlkAttr
.NO_INTERIOR
);
72 this () nothrow @safe @nogc {}
73 this (string aname
, bool aoriented
=true) nothrow @safe @nogc { mName
= aname
; mOriented
= aoriented
; }
74 this (string aname
, in GengPatternPoints apat
, bool aoriented
) nothrow @safe @nogc {
78 mOriented
= aoriented
;
82 @property bool valid () const pure nothrow @safe @nogc { pragma(inline
, true); return (mNormalized || points
.length
>= 4); }
83 @property bool normalized () const pure nothrow @safe @nogc { pragma(inline
, true); return mNormalized
; }
84 @property bool oriented () const pure nothrow @safe @nogc { pragma(inline
, true); return mOriented
; }
85 @property void oriented (bool v
) pure nothrow @safe @nogc { pragma(inline
, true); if (!mNormalized
) mOriented
= v
; } // can't be changed for normalized glyphs
87 @property string
name () const pure nothrow @safe @nogc { pragma(inline
, true); return mName
; }
88 @property void name (string v
) @safe nothrow @nogc { pragma(inline
, true); mName
= v
; }
90 usize
length () const pure nothrow @safe @nogc { pragma(inline
, true); return (mNormalized ? NormalizedPoints
: points
.length
/2); }
91 alias opDollar
= length
;
93 auto x (usize idx
) const pure nothrow @safe @nogc {
96 return (idx
*2 < points
.length ? points
[idx
*2] : typeof(points
[0]).nan
);
98 return (idx
< NormalizedPoints ? patpoints
[idx
*2] : typeof(points
[0]).nan
);
102 auto y (usize idx
) const pure nothrow @safe @nogc {
103 pragma(inline
, true);
105 return (idx
*2 < points
.length ? points
[idx
*2+1] : typeof(points
[0]).nan
);
107 return (idx
< NormalizedPoints ? patpoints
[idx
*2+1] : typeof(points
[0]).nan
);
111 auto clear () nothrow {
112 if (points
.length
) { points
.length
= 0; points
.assumeSafeAppend
; }
118 auto clone () const {
119 auto res
= new PTGlyph(mName
);
120 res
.mNormalized
= mNormalized
;
121 res
.mOriented
= mOriented
;
122 res
.patpoints
[] = patpoints
[];
123 if (points
.length
> 0) {
124 res
.points
= points
.dup
;
125 normBlkAttr(res
.points
.ptr
);
130 auto addPoint (int x
, int y
) {
131 if (mNormalized
) throw new Exception("can't add point to normalized glyph");
132 if (points
.length
> 0) {
133 // check distance and don't add points that are too close to each other
134 immutable lx
= x
-points
[$-2], ly
= y
-points
[$-1];
135 if (lx
*lx
+ly
*ly
< MinPointDistance
*MinPointDistance
) return this;
137 auto optr
= points
.ptr
;
139 if (optr
!= points
.ptr
) { normBlkAttr(points
.ptr
); optr
= points
.ptr
; }
141 if (optr
!= points
.ptr
) { normBlkAttr(points
.ptr
); optr
= points
.ptr
; }
146 auto normalize (bool dropPoints
=true) {
148 if (points
.length
< 4) throw new Exception("glyph must have at least two points");
149 buildNormPoints(patpoints
, points
, mOriented
);
153 points
.assumeSafeAppend
;
160 GengFloat
match (const(PTGlyph
) sample
) const pure nothrow @safe @nogc {
161 if (sample
is null ||
!sample
.valid ||
!valid
) return -GengFloat
.infinity
;
163 // this is normalized
164 return match(patpoints
, sample
);
166 // this is not normalized
167 GengPatternPoints v1
= void;
168 buildNormPoints(v1
, points
, mOriented
);
169 return match(v1
, sample
);
173 string
patpointsToString () {
174 import std
.string
: format
;
175 return format("%s", patpoints
);
180 static GengFloat
match (in GengPatternPoints tpl
, const(PTGlyph
) sample
) pure nothrow @safe @nogc {
181 if (sample
is null ||
!sample
.valid
) return -GengFloat
.infinity
;
182 if (sample
.mNormalized
) {
183 return match(tpl
, sample
.patpoints
);
185 GengPatternPoints spts
= void;
186 buildNormPoints(spts
, sample
.points
, sample
.mOriented
);
187 return match(tpl
, spts
);
191 static GengFloat
match (in GengPatternPoints v0
, in GengPatternPoints v1
) pure nothrow @safe @nogc {
192 return 1.0/optimalCosineDistance(v0
, v1
);
195 static GengFloat
optimalCosineDistance (in GengPatternPoints v0
, in GengPatternPoints v1
) pure nothrow @safe @nogc {
196 import std
.math
: atan
, acos
, cos
, sin
;
197 GengFloat a
= 0.0, b
= 0.0;
198 foreach (immutable idx
; 0..NormalizedPoints
) {
199 a
+= v0
[idx
*2+0]*v1
[idx
*2+0]+v0
[idx
*2+1]*v1
[idx
*2+1];
200 b
+= v0
[idx
*2+0]*v1
[idx
*2+1]-v0
[idx
*2+1]*v1
[idx
*2+0];
202 immutable GengFloat angle
= atan(b
/a
);
203 return acos(a
*cos(angle
)+b
*sin(angle
));
206 // glyph length (not point counter!)
207 static GengFloat
glyphLength (in GengFloat
[] points
) pure nothrow @safe @nogc {
209 if (points
.length
>= 4) {
210 // don't want to bring std.algo here
211 GengFloat px
= points
[0], py
= points
[1];
212 foreach (immutable idx
; 2..points
.length
/2) {
213 immutable cx
= points
[idx
*2+0], cy
= points
[idx
*2+1];
214 res
+= distance(px
, py
, cx
, cy
);
222 static void resample (out GengPatternPoints ptres
, in GengFloat
[] points
) pure @safe nothrow @nogc {
223 assert(points
.length
>= 4);
224 immutable GengFloat I
= glyphLength(points
)/(NormalizedPoints
-1); // interval length
226 GengFloat prx
= points
[0];
227 GengFloat pry
= points
[1];
228 // add first point as-is
231 usize ptpos
= 2, oppos
= 2;
232 while (oppos
< points
.length
) {
233 immutable GengFloat cx
= points
[oppos
], cy
= points
[oppos
+1];
234 immutable d
= distance(prx
, pry
, cx
, cy
);
236 immutable dd = (I
-D
)/d
;
237 immutable qx
= prx
+dd*(cx
-prx
);
238 immutable qy
= pry
+dd*(cy
-pry
);
239 assert(ptpos
< NormalizedPoints
*2);
242 // use 'q' as previous point
253 // somtimes we fall a rounding-error short of adding the last point, so add it if so
254 if (ptpos
/2 == NormalizedPoints
-1) {
255 ptres
[ptpos
++] = points
[$-2];
256 ptres
[ptpos
++] = points
[$-1];
258 assert(ptpos
== NormalizedPoints
*2);
261 // stroke is not required to be centered, but it must be resampled
262 static void vectorize (out GengPatternPoints vres
, in GengPatternPoints ptx
, bool orientationSensitive
)
263 pure nothrow @safe @nogc
265 import std
.math
: atan2
, cos
, sin
, floor
, sqrt
, PI
;
266 GengPatternPoints pts
;
267 GengFloat indAngle
, delta
;
268 GengFloat cx
= 0.0, cy
= 0.0;
270 foreach (immutable idx
; 0..NormalizedPoints
) {
274 cx
/= NormalizedPoints
;
275 cy
/= NormalizedPoints
;
276 foreach (immutable idx
; 0..NormalizedPoints
) {
277 pts
[idx
*2+0] = ptx
[idx
*2+0]-cx
;
278 pts
[idx
*2+1] = ptx
[idx
*2+1]-cy
;
280 indAngle
= atan2(pts
[1], pts
[0]); // always must be done for centered stroke
281 if (orientationSensitive
) {
282 immutable base_orientation
= (PI
/4.0)*floor((indAngle
+PI
/8.0)/(PI
/4.0));
283 delta
= base_orientation
-indAngle
;
287 immutable GengFloat cosd
= cos(delta
);
288 immutable GengFloat sind
= sin(delta
);
290 foreach (immutable idx
; 0..NormalizedPoints
) {
291 immutable nx
= pts
[idx
*2+0]*cosd
-pts
[idx
*2+1]*sind
;
292 immutable ny
= pts
[idx
*2+1]*cosd
+pts
[idx
*2+0]*sind
;
297 immutable GengFloat magnitude
= sqrt(sum
);
298 foreach (immutable idx
; 0..NormalizedPoints
*2) vres
[idx
] /= magnitude
;
301 static void buildNormPoints (out GengPatternPoints vres
, in GengFloat
[] points
, bool orientationSensitive
)
302 pure nothrow @safe @nogc
304 assert(points
.length
>= 4);
305 GengPatternPoints tmp
= void;
306 resample(tmp
, points
);
307 vectorize(vres
, tmp
, orientationSensitive
);
311 PTGlyph
findMatch(R
) (auto ref R grng
, GengFloat
* outscore
=null) const
312 if (isInputRange
!R
&& !isInfinite
!R
&& is(ElementType
!R
: PTGlyph
))
314 GengFloat bestScore
= -GengFloat
.infinity
;
316 if (outscore
!is null) *outscore
= GengFloat
.nan
;
318 // build normalized `this` glyph in pts
319 GengPatternPoints pts
= void;
320 if (this.mNormalized
) {
321 pts
[] = this.patpoints
[];
323 buildNormPoints(pts
, points
, mOriented
);
325 while (!grng
.empty
) {
326 auto gs
= grng
.front
;
328 if (gs
is null ||
!gs
.valid
) continue;
329 GengFloat score
= match(pts
, gs
);
330 if (score
>= MinGestureMatch
&& score
> bestScore
) {
336 if (res
!is null && outscore
!is null) *outscore
= bestScore
;
342 // ////////////////////////////////////////////////////////////////////////// //
346 public void gstLibLoad(R
) (auto ref R orng
, VFile st
) if (isOutputRange
!(R
, PTGlyph
)) {
347 auto sign
= st
.readNum
!ulong();
348 if (sign
!= 0x304244525450384BUL
&& sign
!= 0x314244525450384BUL
) throw new Exception("invalid library signature"); // "K8PTRDB0"/1
349 ubyte ver
= ((sign
>>56)-0x30)&0xff;
350 auto cnt
= st
.readNum
!uint();
351 if (cnt
> 0x7fff_ffff
) throw new Exception("too many glyphs");
352 if (cnt
== 0) return;
353 foreach (immutable idx
; 0..cnt
) {
355 auto len
= st
.readNum
!uint();
356 if (len
> 1024) throw new Exception("glyph name too long");
359 auto buf
= new char[](len
);
360 st
.rawReadExact(buf
);
361 name
= cast(string
)buf
; // it is safe to cast here
364 GengPatternPoints pts
= void;
365 foreach (ref pt
; pts
) {
366 pt
= st
.readNum
!float();
367 if (pt
!= pt
) throw new Exception("invalid number"); // nan check
369 bool oriented
= true;
370 if (ver
== 1) oriented
= (st
.readNum
!ubyte != 0);
371 auto g
= new PTGlyph(name
, pts
, oriented
);
377 public PTGlyph
[] gstLibLoad (VFile fl
) {
380 void put (PTGlyph g
) nothrow { if (g
!is null && g
.valid
) gls
~= g
; }
384 return (r
.gls
.length ? r
.gls
: null);
388 // ////////////////////////////////////////////////////////////////////////// //
389 public void gstLibSave(R
) (VFile st
, auto ref R grng
) if (isInputRange
!R
&& !isInfinite
!R
&& is(ElementType
!R
: PTGlyph
)) {
390 st
.writeNum
!ulong(0x314244525450384BuL
); // "K8PTRDB1"
391 static if (hasLength
!R
) {
392 auto cnt
= grng
.length
;
393 if (cnt
> 0x7fff_ffff
) throw new Exception("too many glyphs");
394 st
.writeNum
!uint(cast(uint)cnt
);
397 auto cntpos
= st
.tell
;
400 while (!grng
.empty
) {
403 if (g
is null ||
!g
.valid
) throw new Exception("can't save invalid glyph");
405 if (g
.name
.length
> 1024) throw new Exception("glyph name too long");
406 st
.writeNum
!uint(cast(uint)g
.name
.length
);
407 st
.rawWriteExact(g
.name
);
409 GengPatternPoints pts
= void;
411 pts
[] = g
.patpoints
[];
413 g
.buildNormPoints(pts
, g
.points
, g
.mOriented
);
415 foreach (immutable pt
; pts
) st
.writeNum
!float(cast(float)pt
);
416 st
.writeNum
!ubyte(g
.mOriented ?
1 : 0);
417 static if (!hasLength
!R
) {
418 if (cnt
== 0x7fff_ffff
) throw new Exception("too many glyphs");
422 static if (!hasLength
!R
) {
425 st
.writeNum
!uint(cast(uint)cnt
);