2
Vector font tool - Gaz Davidson December 2006-2011
4
I noticed bitmap fonts were taking massive amounts of video memory at reasonable sizes,
5
so I decided to make a vector font. I always wanted to try converting pixels to triangles...
7
And I failed! This is a collection of the ugliest, bloated, most inneficient algorithms
8
i've ever written, but its kinda working so I'm not changing it.
11
#ifndef __VECTOR_FONT_TOOL_INCLUDED__
12
#define __VECTOR_FONT_TOOL_INCLUDED__
15
#include "CFontTool.h"
19
using namespace video;
23
core::array<core::vector2df> positions;
24
core::array<u16> indexes;
26
// for adding one triangle list to another,
27
// these triangles share positions, but dont share triangles
28
STriangleList& operator+=(STriangleList &other)
32
map.set_used(other.positions.size());
34
for (u32 i=0; i<map.size(); ++i)
37
for (u32 i=0; i<positions.size(); ++i)
38
for (u32 j=0; j<map.size(); ++j)
39
if ( positions[i] == other.positions[j] )
42
for (u32 i=0; i<map.size(); ++i)
45
positions.push_back(other.positions[i]);
46
map[i] = positions.size()-1;
50
for (u32 i=0; i<other.indexes.size(); ++i)
51
indexes.push_back((u32)map[other.indexes[i]]);
56
// functions for building triangles for shapes,
57
// each shape can't have duplicate triangles
58
bool hasTriangle(core::vector2df a, core::vector2df b, core::vector2df c)
60
// make sure the triangle is wound correctly
61
if (core::line2df(a,b).getPointOrientation(c) < 0)
62
{ core::vector2df tmp=a; a=b; b=tmp; }
64
u32 ia=0xffffffff, ib=0xffffffff, ic=0xffffffff;
66
for (u32 i=0; i < positions.size() && (ia==(u32)-1||ib==(u32)-1||ic==(u32)-1) ; ++i)
68
if (positions[i] == a)
70
if (positions[i] == b)
72
if (positions[i] == c)
89
for (u32 i=0; i<indexes.size(); i+=3)
90
if ( (indexes[i] == ia && indexes[i+1] == ib && indexes[i+2] == ic) ||
91
(indexes[i] == ic && indexes[i+1] == ia && indexes[i+2] == ib) ||
92
(indexes[i] == ib && indexes[i+1] == ic && indexes[i+2] == ia) )
98
void add(core::vector2df a, core::vector2df b, core::vector2df c)
101
// make sure the triangle is wound correctly
102
if (core::line2df(a,b).getPointOrientation(c) < 0)
104
core::vector2df tmp=a; a=b; b=tmp;
107
u32 ia=0xffffffff, ib=0xffffffff, ic=0xffffffff;
108
// no duplicate vertex positions allowed...
109
for (u32 i=0; i < positions.size() && (ia==-1||ib==-1||ic==-1) ; ++i)
111
if (positions[i] == a)
113
if (positions[i] == b)
115
if (positions[i] == c)
121
ia = positions.size();
122
positions.push_back(a);
127
ib = positions.size();
128
positions.push_back(b);
133
ic = positions.size();
134
positions.push_back(c);
138
// no duplicate triangles allowed
142
for (u32 i=0; i<indexes.size(); i+=3)
144
if ( (indexes[i] == ia && indexes[i+1] == ib && indexes[i+2] == ic) ||
145
(indexes[i] == ic && indexes[i+1] == ia && indexes[i+2] == ib) ||
146
(indexes[i] == ib && indexes[i+1] == ic && indexes[i+2] == ia) )
156
indexes.push_back(ia);
157
indexes.push_back(ib);
158
indexes.push_back(ic);
163
// finds groups of pixels and triangulates them
167
CGroupFinder(bool *memory, s32 w, s32 h, IrrlichtDevice *dev):
168
width(w), height(h), mem(memory), Device(dev)
170
refbuffer.set_used(w*h);
171
for (u32 i=0; i<refbuffer.size(); ++i)
173
// find groups of pixels
178
for (u32 i=0; i<groups.size(); ++i)
180
groups[i].triangulate();
184
// contains a clockwise edge line
187
SEdge() : positions() { }
189
core::array<core::position2di> positions;
191
bool isMember(s32 x, s32 y)
193
for (u32 i=0; i<positions.size(); ++i)
194
if (positions[i].X == x && positions[i].Y == y)
199
// reduces the number of points in the edge
200
void reduce(s32 level=0)
202
// level 0- remove points on the same line
203
for (u32 i=1; i < positions.size()-1; ++i)
205
// same point as the last one?! shouldnt happen, dunno why it does :|
206
if (positions[i-1] == positions[i])
208
positions.erase(i--);
213
core::vector2d<f32> h1((f32)(positions[i-1].X - positions[i].X),(f32)(positions[i-1].Y - positions[i].Y)),
214
h2((f32)(positions[i].X - positions[i+1].X),(f32)(positions[i].Y - positions[i+1].Y));
218
if (h1==h2) // erase the current point
219
positions.erase(i--);
222
// level 1- if point1 points at point3, we can skip point2
223
// level 2+ allow a deviation of level-1
229
// contains an array of lines for triangulation
232
core::array<core::line2df> lines;
233
SLineList() : lines() { }
234
void addEdge(const SEdge &edge)
236
// adds lines to the buffer
237
for (u32 i=1; i<edge.positions.size(); ++i)
238
addLine(core::line2df((f32)edge.positions[i-1].X, (f32)edge.positions[i-1].Y,
239
(f32)edge.positions[i].X, (f32)edge.positions[i].Y ));
241
void addLine( const core::line2df &line )
245
lines.push_back(line);
247
bool hasLine( const core::line2df &line )
249
for (u32 i=0; i<lines.size(); ++i)
250
if (line == lines[i] || (line.start == lines[i].end && line.end == lines[i].start) )
255
bool crossesWith( core::line2df l, core::vector2df p)
257
// inside checks only work with clockwise triangles
258
if (l.getPointOrientation(p) < 0)
259
{ core::vector2df tmp=l.start; l.start=l.end; l.end=tmp; }
261
// make the 3 triangle edges
262
core::line2df &la=l, lb(l.end,p), lc(p,l.start);
264
// test every line in the list
265
for (u32 i=0; i<lines.size(); ++i)
267
core::line2df &l2 = lines[i];
269
// the triangle isn't allowed to enclose any points
270
// triangles are clockwise, so if to the right of all 3 lines, it's enclosed
271
if (la.getPointOrientation(l2.start) > 0 &&
272
lb.getPointOrientation(l2.start) > 0 &&
273
lc.getPointOrientation(l2.start) > 0)
275
//if (la.getPointOrientation(l2.start) < 0 &&
276
// lb.getPointOrientation(l2.start) < 0 &&
277
// lc.getPointOrientation(l2.start) < 0)
281
//if (la.intersectWith(l2,out))
282
// if (out != la.start && out != la.end &&
283
// out != l2.start && out != l2.end)
285
if (lb.intersectWith(l2,out))
286
if (!out.equals(lb.start) && !out.equals(lb.end) &&
287
!out.equals(l2.start) && !out.equals(l2.end))
289
if (lc.intersectWith(l2,out))
290
if (!out.equals(lc.start) && !out.equals(lc.end) &&
291
!out.equals(l2.start) && !out.equals(l2.end))
294
// my shit intersection code only works with lines in certain directions :(
295
if (l2.intersectWith(lb,out))
296
if (!out.equals(lb.start) && !out.equals(lb.end) &&
297
!out.equals(l2.start) && !out.equals(l2.end))
299
if (l2.intersectWith(lc,out))
300
if (!out.equals(lc.start) && !out.equals(lc.end) &&
301
!out.equals(l2.start) && !out.equals(l2.end))
305
if (lb.isPointOnLine(l2.start) && l2.start != lb.start && l2.start != lb.end)
307
if (lc.isPointOnLine(l2.start) && l2.start != lc.start && l2.start != lc.end)
315
// an area of adjacent pixels
318
SPixelGroup(IrrlichtDevice *device) : triangles(), pixelWidth(0), pixelHeight(0),
321
core::array<core::position2di> pixels;
322
core::array<SEdge> edges;
323
STriangleList triangles;
324
core::array<SLineList> ll;
325
core::array<bool> isMemberCache;
328
IrrlichtDevice *Device;
333
// find edges in this group
336
// triangulate the group
341
void drawTriangle( core::line2df line, core::vector2df point)
343
//const u32 endt = Device->getTimer()->getTime() + t;
347
//while(Device->getTimer()->getTime() < endt )
350
Device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
351
for (u32 v=0;v<ll.size(); ++v)
352
for (u32 h=0;h<ll[v].lines.size(); ++h)
354
core::line2df ¤tline = ll[v].lines[h];
355
core::position2di st = core::position2di((s32)(currentline.start.X*scale)+50, (s32)(currentline.start.Y*scale)+50);
356
core::position2di en = core::position2di((s32)(currentline.end.X*scale)+50, (s32)(currentline.end.Y*scale)+50);
358
Device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,255,255));
360
// draw this triangle
361
const core::position2di st((s32)(line.start.X*scale)+50, (s32)(line.start.Y*scale)+50);
362
const core::position2di en((s32)(line.end.X*scale)+50, (s32)(line.end.Y*scale)+50);
363
const core::position2di p((s32)(point.X*scale)+50, (s32)(point.Y*scale)+50);
364
Device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
365
Device->getVideoDriver()->draw2DLine(en,p, SColor(255,0,255,0));
366
Device->getVideoDriver()->draw2DLine(p,st, SColor(255,0,0,255));
368
Device->getVideoDriver()->endScene();
374
// make lines from edges, because they're easier to deal with
376
for (u32 i=0; i < edges.size(); ++i)
382
// add an extra one for inside edges
383
SLineList innerlines;
384
ll.push_back(innerlines);
386
// loop through each edge and make triangles
387
for (u32 i=0; i<ll.size(); ++i)
389
// loop through each line in the edge
390
for (u32 cl=0; cl<ll[i].lines.size(); ++cl)
393
core::line2df ¤tLine = ll[i].lines[cl];
394
f32 bestScore = -10.0f;
397
// find the best scoring point to join to this line
398
for (u32 k=0; k<ll.size(); ++k)
399
for (u32 j=0; j< ll[k].lines.size(); ++j)
402
core::vector2df point(ll[k].lines[j].start.X,
403
ll[k].lines[j].start.Y);
404
core::line2df line1(point,currentLine.start);
405
core::line2df line2(currentLine.end,point);
407
// can't be part of the current line
408
if (point == currentLine.start || point == currentLine.end)
411
// must be to the right hand side (triangles are wound clockwise)
412
// unless its part of the inside...
413
f32 side1 = currentLine.getPointOrientation(point);
414
f32 side2 = core::line2df(point,currentLine.start).getPointOrientation(currentLine.end);
415
f32 side3 = core::line2df(currentLine.end,point).getPointOrientation(currentLine.start);
417
if (side1 <= 0 || side2 <= 0 || side3 <=0)
420
// can't already have this triangle
421
if (triangles.hasTriangle(currentLine.start,currentLine.end,point))
424
// must not cross any other lines or enclose any points
425
bool itCrossed = false;
426
for (u32 v=0; v<ll.size(); ++v)
427
if (ll[v].crossesWith(currentLine, point))
436
// so, we like this triangle, but how much?
437
// is it better than all the others?
439
// we prefer points from other edges, unless its on the inside
440
if (k==i && i != ll.size()-1)
445
// we prefer evenly shaped triangles
447
// we prefer triangles with a large area
449
// do we like this one more than the others?
457
// hopefully we found one
458
if (bestEdge >= 0 && bestPoint >= 0 && bestScore >= 0.0f)
460
//assert(bestEdge >= 0 && bestPoint >= 0);
461
//assert(bestScore >= 0.0f);
463
core::vector2df point(ll[bestEdge].lines[bestPoint].start.X, ll[bestEdge].lines[bestPoint].start.Y);
465
// add it to the triangles list
466
triangles.add(currentLine.start, currentLine.end, point);
468
// add inner lines to the line buffer, but only if they arent in others
470
core::line2df la(point,currentLine.start);
471
core::line2df lb(currentLine.end,point);
474
for (u32 lineno=0;lineno<ll.size()-1; ++lineno)
475
if (ll[lineno].hasLine(la))
476
{ found=true; break; }
478
ll[ll.size()-1].addLine(la);
480
for (u32 lineno=0;lineno<ll.size()-1; ++lineno)
481
if (ll[lineno].hasLine(lb))
482
{ found=true; break; }
484
ll[ll.size()-1].addLine(lb);
486
//drawTriangle(currentLine, point);
499
refreshIsMemberCache();
504
// loop through each pixel
505
for (u32 i=0; i < pixels.size(); ++i)
507
core::position2di &p = pixels[i];
509
bool ul = isMember(p.X-1,p.Y-1);
510
bool u = isMember(p.X,p.Y-1);
511
bool ur = isMember(p.X+1,p.Y-1);
512
bool l = isMember(p.X-1,p.Y);
513
bool r = isMember(p.X+1,p.Y);
514
bool bl = isMember(p.X-1,p.Y+1);
515
bool b = isMember(p.X,p.Y+1);
516
bool br = isMember(p.X+1,p.Y+1);
518
// walls already added?
519
bool top=u, bottom=b, left=l, right=r;
521
if (!(ul | u | ur | l | r | bl | b | br))
525
a.positions.push_back( core::position2di(x,y));
526
a.positions.push_back( core::position2di(x+1,y));
527
a.positions.push_back( core::position2di(x+1,y+1));
528
a.positions.push_back( core::position2di(x,y+1));
529
a.positions.push_back( core::position2di(x,y));
531
top=bottom=left=right=true;
535
if (!(ul|u|l) && (b&r) )
537
// upper outer diagonal "/"
538
addToEdges(x,y+1,x+1,y);
540
} else if ( !(u|ur|r) && (b&l) )
542
// upper outer diagonal "\"
543
addToEdges(x,y,x+1,y+1);
545
} else if ( !(l|bl|b) && (r&u) )
547
// lower outer diagonal "\"
548
addToEdges(x+1,y+1,x,y);
550
} else if ( !(r|br|b) && (l&u) )
552
// lower outer diagonal "/"
553
addToEdges(x+1,y,x,y+1);
555
}/* else if (!(b) && (l&bl) )
557
// upper inner diagonal "/"
558
addToEdges(x+1,y+1,x,y+2);
560
} else if ( !(b) && (r&br) )
562
// upper inner diagonal "\"
563
addToEdges(x+1,y+2,x,y+1);
565
} else if ( !(r) && (b&br) )
567
// lower inner diagonal "\"
568
addToEdges(x+1,y,x+2,y+1);
570
} else if ( !(l) && (b&bl) )
572
// lower inner diagonal "/"
573
addToEdges(x-1,y+1,x,y);
578
if (!left /*&& !( (u&ul) || (b&bl)) */) addToEdges(x,y+1,x,y);
579
if (!top /*&& !( (l&ul) || (r&ur)) */) addToEdges(x,y,x+1,y);
580
if (!right /*&& !( (u&ur) || (b&br)) */) addToEdges(x+1,y,x+1,y+1);
581
if (!bottom /*&& !( (l&bl) || (r&br)) */) addToEdges(x+1,y+1,x,y+1);
585
// reduce the number of points in each edge
586
for (u32 i=0; i<edges.size(); ++i)
590
// all edges should have at least 3 points
591
assert(edges[i].positions.size() >= 3);
593
// all edges should be closed
594
assert(edges[i].positions[0] == edges[i].positions[edges[i].positions.size()-1] );
598
// adds a line to the edges arrays
599
void addToEdges(s32 x1, s32 y1, s32 x2, s32 y2)
602
// loop through each edge
603
for (u32 i=0; i<edges.size(); ++i)
605
// if this line starts at the end of an edge
606
if ( edges[i].positions[edges[i].positions.size()-1] == core::position2di(x1,y1))
609
edges[i].positions.push_back(core::position2di(x2,y2));
613
// if the line ends at the start of the edge
614
if ( edges[i].positions[0]== core::position2di(x2,y2))
616
// add it to the front
617
edges[i].positions.push_front(core::position2di(x1,y1));
624
// we make a new edge
626
n.positions.push_back(core::position2di(x1,y1));
627
n.positions.push_back(core::position2di(x2,y2));
636
// touching edges are joined
638
for (u32 i=0; i < edges.size(); ++i)
639
for (u32 j=0; j < edges.size(); ++j)
641
if (i != j && edges[j].positions.size() && edges[i].positions.size())
643
if (edges[j].positions[0] == edges[i].positions[edges[i].positions.size()-1])
645
for (u32 k=0; k < edges[j].positions.size(); ++k)
646
edges[i].positions.push_back(edges[j].positions[k]);
647
edges[j].positions.clear();
652
// remove empty edges
653
for (u32 i=0; i<edges.size(); ++i)
654
if (edges[i].positions.size() == 0)
658
// tells if this x,y position is a member of this group
659
bool isMember(s32 x, s32 y)
661
//for (u32 i=0; i<pixels.size(); ++i)
662
// if (pixels[i].X == x && pixels[i].Y == y)
664
if (x>pixelWidth || y>pixelHeight || x<0 || y<0)
667
return isMemberCache[pixelWidth*y + x];
670
void refreshIsMemberCache()
672
isMemberCache.clear();
673
pixelWidth=0; pixelHeight=0;
674
for (u32 i=0; i<pixels.size(); ++i)
676
if (pixels[i].X>pixelWidth) pixelWidth=pixels[i].X;
677
if (pixels[i].Y>pixelHeight) pixelHeight=pixels[i].Y;
679
pixelWidth+=2; pixelHeight+=2;
680
isMemberCache.set_used(pixelWidth*pixelHeight+1);
681
for (u32 i=0; i<isMemberCache.size(); ++i)
682
isMemberCache[i] = false;
683
for (u32 i=0; i<pixels.size(); ++i)
684
isMemberCache[pixelWidth*pixels[i].Y + pixels[i].X] = true;
689
void drawEdges(IrrlichtDevice *device, u32 t, s32 scale)
691
const u32 stt = device->getTimer()->getTime();
692
const u32 endt = stt + t;
694
while(device->getTimer()->getTime() < endt )
696
const f32 phase = f32((device->getTimer()->getTime()-stt) % 500) / 500.0f;
699
device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
700
for (u32 g=0;g<groups.size(); ++g)
701
for (u32 v=0;v<groups[g].edges.size(); ++v)
702
for (u32 p=1;p<groups[g].edges[v].positions.size(); ++p)
704
core::position2di st = core::position2di(groups[g].edges[v].positions[p-1].X*scale+50, groups[g].edges[v].positions[p-1].Y*scale+50) ;
705
core::position2di en = core::position2di(groups[g].edges[v].positions[p].X*scale+50, groups[g].edges[v].positions[p].Y*scale+50) ;
706
core::position2di ep = en-st;
707
ep = st + core::position2di((s32)(ep.X*phase), (s32)(ep.Y*phase));
708
device->getVideoDriver()->draw2DLine(st,en);
709
device->getVideoDriver()->draw2DLine(st,ep,video::SColor(255,255,0,0) );
711
device->getVideoDriver()->endScene();
715
void drawTriangles(IrrlichtDevice *device, u32 t, s32 scale)
717
const u32 stt = device->getTimer()->getTime();
718
const u32 endt = stt + t;
720
while(device->getTimer()->getTime() < endt )
722
const f32 phase = f32((device->getTimer()->getTime()-stt) % 500) / 500.0f;
725
device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
726
for (u32 g=0;g<groups.size(); ++g)
727
for (u32 v=0;v<groups[g].triangles.indexes.size()*phase; v+=3)
729
STriangleList &t = groups[g].triangles;
730
core::position2di st((s32)(t.positions[t.indexes[v+0]].X*scale)+50,(s32)(t.positions[t.indexes[v+0]].Y*scale)+50);
731
core::position2di en((s32)(t.positions[t.indexes[v+1]].X*scale)+50,(s32)(t.positions[t.indexes[v+1]].Y*scale)+50);
732
device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
733
st = core::position2di((s32)(t.positions[t.indexes[v+1]].X*scale)+50,(s32)(t.positions[t.indexes[v+1]].Y*scale)+50);
734
en = core::position2di((s32)(t.positions[t.indexes[v+2]].X*scale)+50,(s32)(t.positions[t.indexes[v+2]].Y*scale)+50);
735
device->getVideoDriver()->draw2DLine(st,en, SColor(255,0,255,0));
736
st = core::position2di((s32)(t.positions[t.indexes[v+2]].X*scale)+50,(s32)(t.positions[t.indexes[v+2]].Y*scale)+50);
737
en = core::position2di((s32)(t.positions[t.indexes[v+0]].X*scale)+50,(s32)(t.positions[t.indexes[v+0]].Y*scale)+50);
738
device->getVideoDriver()->draw2DLine(st,en, SColor(255,0,0,255));
740
device->getVideoDriver()->endScene();
744
void drawTriLines(IrrlichtDevice *device, u32 t, s32 scale)
746
const u32 endt = device->getTimer()->getTime() + t;
748
while(device->getTimer()->getTime() < endt )
751
device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
752
for (u32 g=0;g<groups.size(); ++g)
753
for (u32 v=0;v<groups[g].ll.size()-1; ++v)
754
for (u32 h=0;h<groups[g].ll[v].lines.size(); ++h)
756
core::line2df ¤tline = groups[g].ll[v].lines[h];
757
const core::position2di st((s32)(currentline.start.X*scale)+50, (s32)(currentline.start.Y*scale)+50);
758
const core::position2di en((s32)(currentline.end.X*scale)+50, (s32)(currentline.end.Y*scale)+50);
759
device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
762
device->getVideoDriver()->endScene();
765
void drawTri3D(IrrlichtDevice *device, u32 t)
767
for (u32 g=0;g<groups.size(); ++g)
769
STriangleList &t = groups[g].triangles;
770
core::array<S3DVertex> verts;
772
for(u32 v=0; v< t.positions.size(); ++v)
774
verts.push_back(S3DVertex(
775
-t.positions[v].X, -t.positions[v].Y, -100,
776
0,0,1,SColor(255,255,255,255),0,0));
779
device->getVideoDriver()->drawIndexedTriangleList(verts.pointer(),verts.size(),t.indexes.pointer(), t.indexes.size()/3 );
784
// process all pixels
787
for (int y=0; y<height; ++y)
788
for (int x=0; x<width; ++x)
793
// remove groups with no pixels
796
for (u32 i=0; i<groups.size(); ++i)
797
if (groups[i].pixels.size() == 0)
800
/*for (s32 y=0; y <height; ++y)
803
for (s32 x=0; x <width; ++x)
806
for (i=0; i<groups.size(); ++i)
808
bool k = groups[i].isMember(x,y);
819
// adds a pixel to its area, merging touching areas
820
void processPixel(s32 x, s32 y)
828
if (x>0) // look one behind
833
if (y>0) // look above
853
if (x<width-1) // top right
890
// didn't find a group for this pixel, so we add one
893
SPixelGroup p(Device);
894
p.pixels.push_back(core::position2di(x,y));
896
groupRefs.push_back(groups.size());
901
groups[groupRefs[grp-1]-1].pixels.push_back(core::position2di(x,y));
903
setRef(x,y,groupRefs[grp-1]);
907
bool& getPixel(s32 x, s32 y) { return mem[y*width +x]; }
908
s32& getRef(s32 x, s32 y) { return refbuffer[y*width +x]; }
909
void setRef(s32 x, s32 y, s32 g) { refbuffer[y*width +x] = g; }
911
void mergeGroups(s32 g1, s32 g2)
915
// joins two groups together
916
for (u32 i=0; i<groups[g2-1].pixels.size(); ++i)
917
groups[g1-1].pixels.push_back(groups[g2-1].pixels[i]);
918
groups[g2-1].pixels.clear();
919
groupRefs[g2-1] = g1;
923
core::array<SPixelGroup> groups;
924
core::array<s32> groupRefs;
925
core::array<s32> refbuffer;
927
IrrlichtDevice *Device;
930
// creates a simple vector font from a bitmap from the font tool
931
class CVectorFontTool
934
CVectorFontTool(CFontTool *fonttool) :
935
triangulator(0), FontTool(fonttool),
936
letterHeight(0), letterWidth(0), triangles()
938
core::map<wchar_t, u32>::Iterator it = FontTool->CharMap.getIterator();
942
CFontTool::SFontArea &fa = FontTool->Areas[(*it).getValue()];
944
if (fa.rectangle.getWidth() > letterWidth)
945
letterWidth = fa.rectangle.getWidth();
946
if (fa.rectangle.getHeight() > letterHeight)
947
letterHeight = fa.rectangle.getHeight();
952
// number of verts is one more than number of pixels because it's a grid of squares
956
// create image memory
957
imagedata.set_used(letterWidth*letterHeight);
959
// create vertex list, set position etc
960
verts.set_used(letterWidth*letterHeight);
961
for (s32 y=0; y<letterHeight; ++y)
963
for (s32 x=0; x<letterWidth; ++x)
965
S3DVertex &v = getVert(x,y);
966
v.Pos = core::vector3df((f32)x,(f32)y,0.0f);
967
v.TCoords.X = (f32)letterWidth / (f32)x;
968
v.TCoords.Y = (f32)letterHeight / (f32)y;
969
v.Normal = core::vector3df(0,0,-1);
970
v.Color = SColor(255,255,255,255);
976
// create each char in the font...
977
it = FontTool->CharMap.getIterator();
980
addChar((*it).getKey());
991
void addChar(wchar_t thischar)
993
const s32 area = FontTool->CharMap[thischar];
994
const CFontTool::SFontArea &fa = FontTool->Areas[area];
996
const s32 img = fa.sourceimage;
997
const core::rect<s32>& r = fa.rectangle;
1000
IImage *image = FontTool->currentImages[img];
1001
for (u32 i=0; i < imagedata.size(); ++i)
1002
imagedata[i] = false;
1003
for (s32 y=r.UpperLeftCorner.Y; y < r.LowerRightCorner.Y; ++y)
1005
for (s32 x=r.UpperLeftCorner.X; x < r.LowerRightCorner.X ; ++x)
1006
if (image->getPixel(x,y).getBlue() > 0)
1008
imagedata[letterWidth*(y-r.UpperLeftCorner.Y) +(x-r.UpperLeftCorner.X)] = true;
1013
triangulator = new CGroupFinder(imagedata.pointer(), letterWidth, letterHeight, FontTool->Device );
1015
wprintf(L"Created character '%c' in texture %d\n", thischar, img );
1017
//floodfill->drawEdges(FontTool->Device, 500, 3);
1018
//floodfill->drawTriangles(FontTool->Device, 500,30);
1019
//floodfill->drawTriLines(FontTool->Device, 200,3);
1022
if (area==32 && map == 0)
1024
scene::ISceneManager *smgr = FontTool->Device->getSceneManager();
1025
smgr->addCameraSceneNodeFPS();
1026
while(FontTool->Device->run())
1028
//floodfill->drawEdges(FontTool->Device, 100, 30);
1029
FontTool->Device->getVideoDriver()->beginScene(true, true, video::SColor(0,200,200,200));
1031
floodfill->drawTri3D(FontTool->Device, 100);
1032
FontTool->Device->getVideoDriver()->endScene();
1036
u32 lastind = triangles.indexes.size();
1038
// loop through each shape and add it to the current character...
1039
for (u32 i=0; i < triangulator->groups.size(); ++i)
1040
triangles += triangulator->groups[i].triangles;
1042
// add character details
1043
charstarts.push_back(lastind);
1044
charlengths.push_back(triangles.indexes.size() - lastind);
1045
chars.push_back(thischar);
1048
bool saveVectorFont(const c8 *filename, const c8 *formatname)
1050
IrrlichtDevice *Device = FontTool->Device;
1052
if (triangles.indexes.size() == 0)
1054
Device->getLogger()->log("No vector data to write, aborting.");
1058
core::stringc fn = filename;
1060
if (core::stringc(formatname) == core::stringc("xml"))
1063
io::IXMLWriter *writer = FontTool->Device->getFileSystem()->createXMLWriter(fn.c_str());
1065
// header and line breaks
1066
writer->writeXMLHeader();
1067
writer->writeLineBreak();
1069
// write info header
1070
writer->writeElement(L"font", false, L"type", L"vector");
1071
writer->writeLineBreak();
1072
writer->writeLineBreak();
1074
// write each letter
1076
for (u32 n=0; n<chars.size(); ++n)
1078
u32 i = FontTool->CharMap[chars[n]];
1079
CFontTool::SFontArea &fa = FontTool->Areas[i];
1083
core::stringw area, under, over;
1084
area = core::stringw(fa.rectangle.LowerRightCorner.X-
1085
fa.rectangle.UpperLeftCorner.X);
1087
area += fa.rectangle.LowerRightCorner.Y-
1088
fa.rectangle.UpperLeftCorner.Y;
1091
core::array<core::stringw> names;
1092
core::array<core::stringw> values;
1096
names.push_back(core::stringw(L"c"));
1097
values.push_back(core::stringw(c));
1100
names.push_back(core::stringw(L"wh"));
1101
values.push_back(area);
1104
names.push_back(core::stringw(L"st"));
1105
values.push_back(core::stringw(charstarts[n]));
1107
names.push_back(core::stringw(L"len"));
1108
values.push_back(core::stringw(charlengths[n]));
1110
if (fa.underhang != 0)
1112
under = core::stringw(fa.underhang);
1113
names.push_back(core::stringw(L"u"));
1114
values.push_back(under);
1116
if (fa.overhang != 0)
1118
over = core::stringw(fa.overhang);
1119
names.push_back(core::stringw(L"o"));
1120
values.push_back(over);
1122
writer->writeElement(L"c", true, names, values);
1124
writer->writeLineBreak();
1127
// write vertex data
1128
core::stringw data, count;
1130
count = core::stringw(triangles.positions.size());
1131
for (u32 i=0; i<triangles.positions.size(); ++i)
1135
data += (s32)triangles.positions[i].X;
1137
data += (s32)triangles.positions[i].Y;
1139
writer->writeElement(L"Vertices", true, L"count", count.c_str(), L"data", data.c_str());
1140
writer->writeLineBreak();
1144
count = core::stringw(triangles.indexes.size());
1145
for (u32 i=0; i<triangles.indexes.size(); i+=3)
1149
data += triangles.indexes[i+0];
1151
data += triangles.indexes[i+1],
1153
data += triangles.indexes[i+2];
1156
writer->writeElement(L"Indices", true, L"count", count.c_str(), L"data", data.c_str());
1157
writer->writeLineBreak();
1159
writer->writeClosingTag(L"font");
1163
Device->getLogger()->log("Font saved.");
1167
else if (core::stringc(formatname) == core::stringc("bin"))
1169
FontTool->Device->getLogger()->log("binary fonts not supported yet, sorry");
1174
FontTool->Device->getLogger()->log("unsupported file format, unable to save vector font");
1179
S3DVertex& getVert(s32 x, s32 y) { return verts[letterWidth*y +x]; }
1181
core::array<S3DVertex> verts;
1182
core::array<u16> inds;
1183
core::array<bool> imagedata;
1185
core::array<s32> charstarts; // start position of each char
1186
core::array<s32> charlengths; // index count
1187
core::array<wchar_t> chars; // letters
1189
CGroupFinder* triangulator;
1190
CFontTool* FontTool;
1195
STriangleList triangles;
1198
#endif // __VECTOR_FONT_TOOL_INCLUDED__