summaryrefslogtreecommitdiff
path: root/tools/IrrFontTool/newFontTool/CVectorFontTool.h
diff options
context:
space:
mode:
Diffstat (limited to 'tools/IrrFontTool/newFontTool/CVectorFontTool.h')
-rw-r--r--tools/IrrFontTool/newFontTool/CVectorFontTool.h1199
1 files changed, 1199 insertions, 0 deletions
diff --git a/tools/IrrFontTool/newFontTool/CVectorFontTool.h b/tools/IrrFontTool/newFontTool/CVectorFontTool.h
new file mode 100644
index 0000000..05a9c79
--- /dev/null
+++ b/tools/IrrFontTool/newFontTool/CVectorFontTool.h
@@ -0,0 +1,1199 @@
+/*
+ Vector font tool - Gaz Davidson December 2006-2012
+
+ I noticed bitmap fonts were taking massive amounts of video memory at reasonable sizes,
+ so I decided to make a vector font. I always wanted to try converting pixels to triangles...
+
+ And I failed! This is a collection of the ugliest, bloated, most inneficient algorithms
+ i've ever written, but its kinda working so I'm not changing it.
+*/
+
+#ifndef __VECTOR_FONT_TOOL_INCLUDED__
+#define __VECTOR_FONT_TOOL_INCLUDED__
+
+#include "irrlicht.h"
+#include "CFontTool.h"
+#include <assert.h>
+
+using namespace irr;
+using namespace video;
+
+struct STriangleList
+{
+ core::array<core::vector2df> positions;
+ core::array<u16> indexes;
+
+ // for adding one triangle list to another,
+ // these triangles share positions, but dont share triangles
+ STriangleList& operator+=(STriangleList &other)
+ {
+ core::matrix4 m;
+ core::array<s32> map;
+ map.set_used(other.positions.size());
+
+ for (u32 i=0; i<map.size(); ++i)
+ map[i]=-1;
+
+ for (u32 i=0; i<positions.size(); ++i)
+ for (u32 j=0; j<map.size(); ++j)
+ if ( positions[i] == other.positions[j] )
+ map[j] = i;
+
+ for (u32 i=0; i<map.size(); ++i)
+ if (map[i] == -1)
+ {
+ positions.push_back(other.positions[i]);
+ map[i] = positions.size()-1;
+ }
+
+ // add triangles
+ for (u32 i=0; i<other.indexes.size(); ++i)
+ indexes.push_back((u32)map[other.indexes[i]]);
+
+ return *this;
+ }
+
+ // functions for building triangles for shapes,
+ // each shape can't have duplicate triangles
+ bool hasTriangle(core::vector2df a, core::vector2df b, core::vector2df c)
+ {
+ // make sure the triangle is wound correctly
+ if (core::line2df(a,b).getPointOrientation(c) < 0)
+ { core::vector2df tmp=a; a=b; b=tmp; }
+
+ u32 ia=0xffffffff, ib=0xffffffff, ic=0xffffffff;
+ // Find each vertex
+ for (u32 i=0; i < positions.size() && (ia==(u32)-1||ib==(u32)-1||ic==(u32)-1) ; ++i)
+ {
+ if (positions[i] == a)
+ ia = i;
+ if (positions[i] == b)
+ ib = i;
+ if (positions[i] == c)
+ ic = i;
+ }
+
+ if (ia==0xffffffff)
+ {
+ return false;
+ }
+ if (ib==0xffffffff)
+ {
+ return false;
+ }
+ if (ic==0xffffffff)
+ {
+ return false;
+ }
+
+ for (u32 i=0; i<indexes.size(); i+=3)
+ if ( (indexes[i] == ia && indexes[i+1] == ib && indexes[i+2] == ic) ||
+ (indexes[i] == ic && indexes[i+1] == ia && indexes[i+2] == ib) ||
+ (indexes[i] == ib && indexes[i+1] == ic && indexes[i+2] == ia) )
+ return true;
+
+ return false;
+ }
+
+ void add(core::vector2df a, core::vector2df b, core::vector2df c)
+ {
+
+ // make sure the triangle is wound correctly
+ if (core::line2df(a,b).getPointOrientation(c) < 0)
+ {
+ core::vector2df tmp=a; a=b; b=tmp;
+ }
+
+ u32 ia=0xffffffff, ib=0xffffffff, ic=0xffffffff;
+ // no duplicate vertex positions allowed...
+ for (u32 i=0; i < positions.size() && (ia==-1||ib==-1||ic==-1) ; ++i)
+ {
+ if (positions[i] == a)
+ ia = i;
+ if (positions[i] == b)
+ ib = i;
+ if (positions[i] == c)
+ ic = i;
+ }
+ bool found=true;
+ if (ia==0xffffffff)
+ {
+ ia = positions.size();
+ positions.push_back(a);
+ found=false;
+ }
+ if (ib==0xffffffff)
+ {
+ ib = positions.size();
+ positions.push_back(b);
+ found=false;
+ }
+ if (ic==0xffffffff)
+ {
+ ic = positions.size();
+ positions.push_back(c);
+ found=false;
+ }
+
+ // no duplicate triangles allowed
+ if (found)
+ {
+ found=false;
+ for (u32 i=0; i<indexes.size(); i+=3)
+ {
+ if ( (indexes[i] == ia && indexes[i+1] == ib && indexes[i+2] == ic) ||
+ (indexes[i] == ic && indexes[i+1] == ia && indexes[i+2] == ib) ||
+ (indexes[i] == ib && indexes[i+1] == ic && indexes[i+2] == ia) )
+ {
+ found=true;
+ break;
+ }
+ }
+ }
+
+ if (!found)
+ {
+ indexes.push_back(ia);
+ indexes.push_back(ib);
+ indexes.push_back(ic);
+ }
+ }
+};
+
+// finds groups of pixels and triangulates them
+class CGroupFinder
+{
+public:
+ CGroupFinder(bool *memory, s32 w, s32 h, IrrlichtDevice *dev):
+ width(w), height(h), mem(memory), Device(dev)
+ {
+ refbuffer.set_used(w*h);
+ for (u32 i=0; i<refbuffer.size(); ++i)
+ refbuffer[i]=0;
+ // find groups of pixels
+ findGroups();
+ removeGroups();
+
+ // triangulate
+ for (u32 i=0; i<groups.size(); ++i)
+ {
+ groups[i].triangulate();
+ }
+ }
+
+ // contains a clockwise edge line
+ struct SEdge
+ {
+ SEdge() : positions() { }
+
+ core::array<core::position2di> positions;
+
+ bool isMember(s32 x, s32 y)
+ {
+ for (u32 i=0; i<positions.size(); ++i)
+ if (positions[i].X == x && positions[i].Y == y)
+ return true;
+ return false;
+ }
+
+ // reduces the number of points in the edge
+ void reduce(s32 level=0)
+ {
+ // level 0- remove points on the same line
+ for (u32 i=1; i < positions.size()-1; ++i)
+ {
+ // same point as the last one?! shouldnt happen, dunno why it does :|
+ if (positions[i-1] == positions[i])
+ {
+ positions.erase(i--);
+ continue;
+ }
+
+ // get headings
+ core::vector2d<f32> h1((f32)(positions[i-1].X - positions[i].X),(f32)(positions[i-1].Y - positions[i].Y)),
+ h2((f32)(positions[i].X - positions[i+1].X),(f32)(positions[i].Y - positions[i+1].Y));
+ h1.normalize();
+ h2.normalize();
+
+ if (h1==h2) // erase the current point
+ positions.erase(i--);
+ }
+
+ // level 1- if point1 points at point3, we can skip point2
+ // level 2+ allow a deviation of level-1
+
+ }
+
+ };
+
+ // contains an array of lines for triangulation
+ struct SLineList
+ {
+ core::array<core::line2df> lines;
+ SLineList() : lines() { }
+ void addEdge(const SEdge &edge)
+ {
+ // adds lines to the buffer
+ for (u32 i=1; i<edge.positions.size(); ++i)
+ addLine(core::line2df((f32)edge.positions[i-1].X, (f32)edge.positions[i-1].Y,
+ (f32)edge.positions[i].X, (f32)edge.positions[i].Y ));
+ }
+ void addLine( const core::line2df &line )
+ {
+ // no dupes!
+ if (!hasLine(line))
+ lines.push_back(line);
+ }
+ bool hasLine( const core::line2df &line )
+ {
+ for (u32 i=0; i<lines.size(); ++i)
+ if (line == lines[i] || (line.start == lines[i].end && line.end == lines[i].start) )
+ return true;
+ return false;
+ }
+
+ bool crossesWith( core::line2df l, core::vector2df p)
+ {
+ // inside checks only work with clockwise triangles
+ if (l.getPointOrientation(p) < 0)
+ { core::vector2df tmp=l.start; l.start=l.end; l.end=tmp; }
+
+ // make the 3 triangle edges
+ core::line2df &la=l, lb(l.end,p), lc(p,l.start);
+
+ // test every line in the list
+ for (u32 i=0; i<lines.size(); ++i)
+ {
+ core::line2df &l2 = lines[i];
+
+ // the triangle isn't allowed to enclose any points
+ // triangles are clockwise, so if to the right of all 3 lines, it's enclosed
+ if (la.getPointOrientation(l2.start) > 0 &&
+ lb.getPointOrientation(l2.start) > 0 &&
+ lc.getPointOrientation(l2.start) > 0)
+ return true;
+ //if (la.getPointOrientation(l2.start) < 0 &&
+ // lb.getPointOrientation(l2.start) < 0 &&
+ // lc.getPointOrientation(l2.start) < 0)
+ // return true;
+
+ core::vector2df out;
+ //if (la.intersectWith(l2,out))
+ // if (out != la.start && out != la.end &&
+ // out != l2.start && out != l2.end)
+ // return true;
+ if (lb.intersectWith(l2,out))
+ if (!out.equals(lb.start) && !out.equals(lb.end) &&
+ !out.equals(l2.start) && !out.equals(l2.end))
+ return true;
+ if (lc.intersectWith(l2,out))
+ if (!out.equals(lc.start) && !out.equals(lc.end) &&
+ !out.equals(l2.start) && !out.equals(l2.end))
+ return true;
+
+ // my shit intersection code only works with lines in certain directions :(
+ if (l2.intersectWith(lb,out))
+ if (!out.equals(lb.start) && !out.equals(lb.end) &&
+ !out.equals(l2.start) && !out.equals(l2.end))
+ return true;
+ if (l2.intersectWith(lc,out))
+ if (!out.equals(lc.start) && !out.equals(lc.end) &&
+ !out.equals(l2.start) && !out.equals(l2.end))
+ return true;
+
+
+ if (lb.isPointOnLine(l2.start) && l2.start != lb.start && l2.start != lb.end)
+ return true;
+ if (lc.isPointOnLine(l2.start) && l2.start != lc.start && l2.start != lc.end)
+ return true;
+
+ }
+ return false;
+ }
+ };
+
+ // an area of adjacent pixels
+ struct SPixelGroup
+ {
+ SPixelGroup(IrrlichtDevice *device) : triangles(), pixelWidth(0), pixelHeight(0),
+ Device(device) {}
+
+ core::array<core::position2di> pixels;
+ core::array<SEdge> edges;
+ STriangleList triangles;
+ core::array<SLineList> ll;
+ core::array<bool> isMemberCache;
+ s32 pixelWidth;
+ s32 pixelHeight;
+ IrrlichtDevice *Device;
+
+ void triangulate()
+ {
+
+ // find edges in this group
+ makeEdges();
+
+ // triangulate the group
+ makeTriangles();
+
+ }
+
+ void drawTriangle( core::line2df line, core::vector2df point)
+ {
+ //const u32 endt = Device->getTimer()->getTime() + t;
+ f32 scale = 5;
+
+
+ //while(Device->getTimer()->getTime() < endt )
+ //{
+ Device->run();
+ Device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
+ for (u32 v=0;v<ll.size(); ++v)
+ for (u32 h=0;h<ll[v].lines.size(); ++h)
+ {
+ core::line2df &currentline = ll[v].lines[h];
+ core::position2di st = core::position2di((s32)(currentline.start.X*scale)+50, (s32)(currentline.start.Y*scale)+50);
+ core::position2di en = core::position2di((s32)(currentline.end.X*scale)+50, (s32)(currentline.end.Y*scale)+50);
+
+ Device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,255,255));
+ }
+ // draw this triangle
+ const core::position2di st((s32)(line.start.X*scale)+50, (s32)(line.start.Y*scale)+50);
+ const core::position2di en((s32)(line.end.X*scale)+50, (s32)(line.end.Y*scale)+50);
+ const core::position2di p((s32)(point.X*scale)+50, (s32)(point.Y*scale)+50);
+ Device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
+ Device->getVideoDriver()->draw2DLine(en,p, SColor(255,0,255,0));
+ Device->getVideoDriver()->draw2DLine(p,st, SColor(255,0,0,255));
+
+ Device->getVideoDriver()->endScene();
+ //}
+ }
+
+ void makeTriangles()
+ {
+ // make lines from edges, because they're easier to deal with
+ ll.clear();
+ for (u32 i=0; i < edges.size(); ++i)
+ {
+ SLineList l;
+ l.addEdge(edges[i]);
+ ll.push_back(l);
+ }
+ // add an extra one for inside edges
+ SLineList innerlines;
+ ll.push_back(innerlines);
+
+ // loop through each edge and make triangles
+ for (u32 i=0; i<ll.size(); ++i)
+ {
+ // loop through each line in the edge
+ for (u32 cl=0; cl<ll[i].lines.size(); ++cl)
+ {
+
+ core::line2df &currentLine = ll[i].lines[cl];
+ f32 bestScore = -10.0f;
+ s32 bestEdge = -1;
+ s32 bestPoint = -1;
+ // find the best scoring point to join to this line
+ for (u32 k=0; k<ll.size(); ++k)
+ for (u32 j=0; j< ll[k].lines.size(); ++j)
+ {
+ f32 score = 0.0f;
+ core::vector2df point(ll[k].lines[j].start.X,
+ ll[k].lines[j].start.Y);
+ core::line2df line1(point,currentLine.start);
+ core::line2df line2(currentLine.end,point);
+
+ // can't be part of the current line
+ if (point == currentLine.start || point == currentLine.end)
+ continue;
+
+ // must be to the right hand side (triangles are wound clockwise)
+ // unless its part of the inside...
+ f32 side1 = currentLine.getPointOrientation(point);
+ f32 side2 = core::line2df(point,currentLine.start).getPointOrientation(currentLine.end);
+ f32 side3 = core::line2df(currentLine.end,point).getPointOrientation(currentLine.start);
+ if (i<ll.size()-1)
+ if (side1 <= 0 || side2 <= 0 || side3 <=0)
+ continue;
+
+ // can't already have this triangle
+ if (triangles.hasTriangle(currentLine.start,currentLine.end,point))
+ continue;
+
+ // must not cross any other lines or enclose any points
+ bool itCrossed = false;
+ for (u32 v=0; v<ll.size(); ++v)
+ if (ll[v].crossesWith(currentLine, point))
+ {
+ itCrossed = true;
+ break;
+ }
+ if (itCrossed)
+ continue;
+
+
+ // so, we like this triangle, but how much?
+ // is it better than all the others?
+
+ // we prefer points from other edges, unless its on the inside
+ if (k==i && i != ll.size()-1)
+ score = 1;
+ else
+ score = 2;
+
+ // we prefer evenly shaped triangles
+
+ // we prefer triangles with a large area
+
+ // do we like this one more than the others?
+ if (score>bestScore)
+ {
+ bestScore = score;
+ bestEdge = k;
+ bestPoint = j;
+ }
+ }
+ // hopefully we found one
+ if (bestEdge >= 0 && bestPoint >= 0 && bestScore >= 0.0f)
+ {
+ //assert(bestEdge >= 0 && bestPoint >= 0);
+ //assert(bestScore >= 0.0f);
+
+ core::vector2df point(ll[bestEdge].lines[bestPoint].start.X, ll[bestEdge].lines[bestPoint].start.Y);
+
+ // add it to the triangles list
+ triangles.add(currentLine.start, currentLine.end, point);
+
+ // add inner lines to the line buffer, but only if they arent in others
+
+ core::line2df la(point,currentLine.start);
+ core::line2df lb(currentLine.end,point);
+
+ bool found = false;
+ for (u32 lineno=0;lineno<ll.size()-1; ++lineno)
+ if (ll[lineno].hasLine(la))
+ { found=true; break; }
+ if (!found)
+ ll[ll.size()-1].addLine(la);
+
+ for (u32 lineno=0;lineno<ll.size()-1; ++lineno)
+ if (ll[lineno].hasLine(lb))
+ { found=true; break; }
+ if (!found)
+ ll[ll.size()-1].addLine(lb);
+
+ //drawTriangle(currentLine, point);
+
+ }
+
+ }
+ }
+ }
+
+ // finds the edges
+ void makeEdges()
+ {
+
+ // speed it up
+ refreshIsMemberCache();
+
+ // clear the edges
+ edges.clear();
+
+ // loop through each pixel
+ for (u32 i=0; i < pixels.size(); ++i)
+ {
+ core::position2di &p = pixels[i];
+ s32 &x=p.X, &y=p.Y;
+ bool ul = isMember(p.X-1,p.Y-1);
+ bool u = isMember(p.X,p.Y-1);
+ bool ur = isMember(p.X+1,p.Y-1);
+ bool l = isMember(p.X-1,p.Y);
+ bool r = isMember(p.X+1,p.Y);
+ bool bl = isMember(p.X-1,p.Y+1);
+ bool b = isMember(p.X,p.Y+1);
+ bool br = isMember(p.X+1,p.Y+1);
+
+ // walls already added?
+ bool top=u, bottom=b, left=l, right=r;
+
+ if (!(ul | u | ur | l | r | bl | b | br))
+ {
+ // lone square
+ SEdge a;
+ a.positions.push_back( core::position2di(x,y));
+ a.positions.push_back( core::position2di(x+1,y));
+ a.positions.push_back( core::position2di(x+1,y+1));
+ a.positions.push_back( core::position2di(x,y+1));
+ a.positions.push_back( core::position2di(x,y));
+ edges.push_back(a);
+ top=bottom=left=right=true;
+ }
+ else
+ {
+ if (!(ul|u|l) && (b&r) )
+ {
+ // upper outer diagonal "/"
+ addToEdges(x,y+1,x+1,y);
+ top=left=true;
+ } else if ( !(u|ur|r) && (b&l) )
+ {
+ // upper outer diagonal "\"
+ addToEdges(x,y,x+1,y+1);
+ top=right=true;
+ } else if ( !(l|bl|b) && (r&u) )
+ {
+ // lower outer diagonal "\"
+ addToEdges(x+1,y+1,x,y);
+ left=bottom=true;
+ } else if ( !(r|br|b) && (l&u) )
+ {
+ // lower outer diagonal "/"
+ addToEdges(x+1,y,x,y+1);
+ right=bottom=true;
+ }/* else if (!(b) && (l&bl) )
+ {
+ // upper inner diagonal "/"
+ addToEdges(x+1,y+1,x,y+2);
+ //bottom=true;
+ } else if ( !(b) && (r&br) )
+ {
+ // upper inner diagonal "\"
+ addToEdges(x+1,y+2,x,y+1);
+ //bottom=true;
+ } else if ( !(r) && (b&br) )
+ {
+ // lower inner diagonal "\"
+ addToEdges(x+1,y,x+2,y+1);
+ //right=true;
+ } else if ( !(l) && (b&bl) )
+ {
+ // lower inner diagonal "/"
+ addToEdges(x-1,y+1,x,y);
+ //left=true;
+ }*/
+
+ // add flat edges
+ if (!left /*&& !( (u&ul) || (b&bl)) */) addToEdges(x,y+1,x,y);
+ if (!top /*&& !( (l&ul) || (r&ur)) */) addToEdges(x,y,x+1,y);
+ if (!right /*&& !( (u&ur) || (b&br)) */) addToEdges(x+1,y,x+1,y+1);
+ if (!bottom /*&& !( (l&bl) || (r&br)) */) addToEdges(x+1,y+1,x,y+1);
+ } // lone square
+ } // for
+
+ // reduce the number of points in each edge
+ for (u32 i=0; i<edges.size(); ++i)
+ {
+ edges[i].reduce(1);
+
+ // all edges should have at least 3 points
+ assert(edges[i].positions.size() >= 3);
+
+ // all edges should be closed
+ assert(edges[i].positions[0] == edges[i].positions[edges[i].positions.size()-1] );
+ }
+ }
+
+ // adds a line to the edges arrays
+ void addToEdges(s32 x1, s32 y1, s32 x2, s32 y2)
+ {
+ bool found=false;
+ // loop through each edge
+ for (u32 i=0; i<edges.size(); ++i)
+ {
+ // if this line starts at the end of an edge
+ if ( edges[i].positions[edges[i].positions.size()-1] == core::position2di(x1,y1))
+ {
+ // add it to the end
+ edges[i].positions.push_back(core::position2di(x2,y2));
+ found=true;
+ break;
+ }
+ // if the line ends at the start of the edge
+ if ( edges[i].positions[0]== core::position2di(x2,y2))
+ {
+ // add it to the front
+ edges[i].positions.push_front(core::position2di(x1,y1));
+ found=true;
+ break;
+ }
+ }
+ if (!found)
+ {
+ // we make a new edge
+ SEdge n;
+ n.positions.push_back(core::position2di(x1,y1));
+ n.positions.push_back(core::position2di(x2,y2));
+ edges.push_back(n);
+ }
+
+ joinEdges();
+ }
+
+ void joinEdges()
+ {
+ // touching edges are joined
+
+ for (u32 i=0; i < edges.size(); ++i)
+ for (u32 j=0; j < edges.size(); ++j)
+ {
+ if (i != j && edges[j].positions.size() && edges[i].positions.size())
+ {
+ if (edges[j].positions[0] == edges[i].positions[edges[i].positions.size()-1])
+ {
+ for (u32 k=0; k < edges[j].positions.size(); ++k)
+ edges[i].positions.push_back(edges[j].positions[k]);
+ edges[j].positions.clear();
+ }
+ }
+ }
+
+ // remove empty edges
+ for (u32 i=0; i<edges.size(); ++i)
+ if (edges[i].positions.size() == 0)
+ edges.erase(i--);
+ }
+
+ // tells if this x,y position is a member of this group
+ bool isMember(s32 x, s32 y)
+ {
+ //for (u32 i=0; i<pixels.size(); ++i)
+ // if (pixels[i].X == x && pixels[i].Y == y)
+ // return true;
+ if (x>pixelWidth || y>pixelHeight || x<0 || y<0)
+ return false;
+ else
+ return isMemberCache[pixelWidth*y + x];
+ }
+
+ void refreshIsMemberCache()
+ {
+ isMemberCache.clear();
+ pixelWidth=0; pixelHeight=0;
+ for (u32 i=0; i<pixels.size(); ++i)
+ {
+ if (pixels[i].X>pixelWidth) pixelWidth=pixels[i].X;
+ if (pixels[i].Y>pixelHeight) pixelHeight=pixels[i].Y;
+ }
+ pixelWidth+=2; pixelHeight+=2;
+ isMemberCache.set_used(pixelWidth*pixelHeight+1);
+ for (u32 i=0; i<isMemberCache.size(); ++i)
+ isMemberCache[i] = false;
+ for (u32 i=0; i<pixels.size(); ++i)
+ isMemberCache[pixelWidth*pixels[i].Y + pixels[i].X] = true;
+ }
+ };
+
+
+ void drawEdges(IrrlichtDevice *device, u32 t, s32 scale)
+ {
+ const u32 stt = device->getTimer()->getTime();
+ const u32 endt = stt + t;
+
+ while(device->getTimer()->getTime() < endt )
+ {
+ const f32 phase = f32((device->getTimer()->getTime()-stt) % 500) / 500.0f;
+
+ device->run();
+ device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
+ for (u32 g=0;g<groups.size(); ++g)
+ for (u32 v=0;v<groups[g].edges.size(); ++v)
+ for (u32 p=1;p<groups[g].edges[v].positions.size(); ++p)
+ {
+ 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) ;
+ core::position2di en = core::position2di(groups[g].edges[v].positions[p].X*scale+50, groups[g].edges[v].positions[p].Y*scale+50) ;
+ core::position2di ep = en-st;
+ ep = st + core::position2di((s32)(ep.X*phase), (s32)(ep.Y*phase));
+ device->getVideoDriver()->draw2DLine(st,en);
+ device->getVideoDriver()->draw2DLine(st,ep,video::SColor(255,255,0,0) );
+ }
+ device->getVideoDriver()->endScene();
+ }
+ }
+
+ void drawTriangles(IrrlichtDevice *device, u32 t, s32 scale)
+ {
+ const u32 stt = device->getTimer()->getTime();
+ const u32 endt = stt + t;
+
+ while(device->getTimer()->getTime() < endt )
+ {
+ const f32 phase = f32((device->getTimer()->getTime()-stt) % 500) / 500.0f;
+
+ device->run();
+ device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
+ for (u32 g=0;g<groups.size(); ++g)
+ for (u32 v=0;v<groups[g].triangles.indexes.size()*phase; v+=3)
+ {
+ STriangleList &t = groups[g].triangles;
+ core::position2di st((s32)(t.positions[t.indexes[v+0]].X*scale)+50,(s32)(t.positions[t.indexes[v+0]].Y*scale)+50);
+ core::position2di en((s32)(t.positions[t.indexes[v+1]].X*scale)+50,(s32)(t.positions[t.indexes[v+1]].Y*scale)+50);
+ device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
+ st = core::position2di((s32)(t.positions[t.indexes[v+1]].X*scale)+50,(s32)(t.positions[t.indexes[v+1]].Y*scale)+50);
+ en = core::position2di((s32)(t.positions[t.indexes[v+2]].X*scale)+50,(s32)(t.positions[t.indexes[v+2]].Y*scale)+50);
+ device->getVideoDriver()->draw2DLine(st,en, SColor(255,0,255,0));
+ st = core::position2di((s32)(t.positions[t.indexes[v+2]].X*scale)+50,(s32)(t.positions[t.indexes[v+2]].Y*scale)+50);
+ en = core::position2di((s32)(t.positions[t.indexes[v+0]].X*scale)+50,(s32)(t.positions[t.indexes[v+0]].Y*scale)+50);
+ device->getVideoDriver()->draw2DLine(st,en, SColor(255,0,0,255));
+ }
+ device->getVideoDriver()->endScene();
+ }
+ }
+
+ void drawTriLines(IrrlichtDevice *device, u32 t, s32 scale)
+ {
+ const u32 endt = device->getTimer()->getTime() + t;
+
+ while(device->getTimer()->getTime() < endt )
+ {
+ device->run();
+ device->getVideoDriver()->beginScene(true,true,video::SColor(0,0,0,0));
+ for (u32 g=0;g<groups.size(); ++g)
+ for (u32 v=0;v<groups[g].ll.size()-1; ++v)
+ for (u32 h=0;h<groups[g].ll[v].lines.size(); ++h)
+ {
+ core::line2df &currentline = groups[g].ll[v].lines[h];
+ const core::position2di st((s32)(currentline.start.X*scale)+50, (s32)(currentline.start.Y*scale)+50);
+ const core::position2di en((s32)(currentline.end.X*scale)+50, (s32)(currentline.end.Y*scale)+50);
+ device->getVideoDriver()->draw2DLine(st,en, SColor(255,255,0,0));
+ }
+
+ device->getVideoDriver()->endScene();
+ }
+ }
+ void drawTri3D(IrrlichtDevice *device, u32 t)
+ {
+ for (u32 g=0;g<groups.size(); ++g)
+ {
+ STriangleList &t = groups[g].triangles;
+ core::array<S3DVertex> verts;
+ verts.clear();
+ for(u32 v=0; v< t.positions.size(); ++v)
+ {
+ verts.push_back(S3DVertex(
+ -t.positions[v].X, -t.positions[v].Y, -100,
+ 0,0,1,SColor(255,255,255,255),0,0));
+ }
+
+ device->getVideoDriver()->drawIndexedTriangleList(verts.pointer(),verts.size(),t.indexes.pointer(), t.indexes.size()/3 );
+ }
+ }
+
+
+ // process all pixels
+ void findGroups()
+ {
+ for (int y=0; y<height; ++y)
+ for (int x=0; x<width; ++x)
+ processPixel(x,y);
+
+ }
+
+ // remove groups with no pixels
+ void removeGroups()
+ {
+ for (u32 i=0; i<groups.size(); ++i)
+ if (groups[i].pixels.size() == 0)
+ groups.erase(i--);
+
+ /*for (s32 y=0; y <height; ++y)
+ {
+ printf("\n");
+ for (s32 x=0; x <width; ++x)
+ {
+ s32 i;
+ for (i=0; i<groups.size(); ++i)
+ {
+ bool k = groups[i].isMember(x,y);
+ if (k)
+ break;
+ }
+ printf("%d",i);
+ }
+ }*/
+
+
+ }
+
+ // adds a pixel to its area, merging touching areas
+ void processPixel(s32 x, s32 y)
+ {
+ // solid?
+ if (getPixel(x,y))
+ {
+ s32 g=0, grp=0;
+
+ bool found=false;
+ if (x>0) // look one behind
+ {
+ grp = getRef(x-1,y);
+ if (grp) found=true;
+ }
+ if (y>0) // look above
+ {
+ if (x>0) // top left
+ {
+ g = getRef(x-1,y-1);
+
+ if (g)
+ {
+ if (found)
+ {
+ mergeGroups(grp, g);
+ }
+ else
+ {
+ grp = g;
+ found = true;
+ }
+ }
+ }
+
+ if (x<width-1) // top right
+ {
+ g = getRef(x+1,y-1);
+
+ if (g)
+ {
+ if (found)
+ {
+ mergeGroups(grp, g);
+ }
+ else
+ {
+ grp = g;
+ found = true;
+ }
+ }
+ }
+
+ // top
+
+ g = getRef(x,y-1);
+
+ if (g)
+ {
+ if (found)
+ {
+ mergeGroups(grp, g);
+ }
+ else
+ {
+ grp = g;
+ found = true;
+ }
+ }
+
+ }
+
+ // didn't find a group for this pixel, so we add one
+ if (!found)
+ {
+ SPixelGroup p(Device);
+ p.pixels.push_back(core::position2di(x,y));
+ groups.push_back(p);
+ groupRefs.push_back(groups.size());
+ grp=groups.size();
+ }
+ else
+ {
+ groups[groupRefs[grp-1]-1].pixels.push_back(core::position2di(x,y));
+ }
+ setRef(x,y,groupRefs[grp-1]);
+ }
+ }
+
+ bool& getPixel(s32 x, s32 y) { return mem[y*width +x]; }
+ s32& getRef(s32 x, s32 y) { return refbuffer[y*width +x]; }
+ void setRef(s32 x, s32 y, s32 g) { refbuffer[y*width +x] = g; }
+
+ void mergeGroups(s32 g1, s32 g2)
+ {
+ if (g1==g2)
+ return;
+ // joins two groups together
+ for (u32 i=0; i<groups[g2-1].pixels.size(); ++i)
+ groups[g1-1].pixels.push_back(groups[g2-1].pixels[i]);
+ groups[g2-1].pixels.clear();
+ groupRefs[g2-1] = g1;
+ }
+
+ s32 width, height;
+ core::array<SPixelGroup> groups;
+ core::array<s32> groupRefs;
+ core::array<s32> refbuffer;
+ bool *mem;
+ IrrlichtDevice *Device;
+};
+
+// creates a simple vector font from a bitmap from the font tool
+class CVectorFontTool
+{
+public:
+ CVectorFontTool(CFontTool *fonttool) :
+ triangulator(0), FontTool(fonttool),
+ letterHeight(0), letterWidth(0), triangles()
+ {
+ core::map<wchar_t, u32>::Iterator it = FontTool->CharMap.getIterator();
+
+ while(!it.atEnd())
+ {
+ CFontTool::SFontArea &fa = FontTool->Areas[(*it).getValue()];
+
+ if (fa.rectangle.getWidth() > letterWidth)
+ letterWidth = fa.rectangle.getWidth();
+ if (fa.rectangle.getHeight() > letterHeight)
+ letterHeight = fa.rectangle.getHeight();
+
+ it++;
+ }
+
+ // number of verts is one more than number of pixels because it's a grid of squares
+ letterWidth++;
+ letterHeight++;
+
+ // create image memory
+ imagedata.set_used(letterWidth*letterHeight);
+
+ // create vertex list, set position etc
+ verts.set_used(letterWidth*letterHeight);
+ for (s32 y=0; y<letterHeight; ++y)
+ {
+ for (s32 x=0; x<letterWidth; ++x)
+ {
+ S3DVertex &v = getVert(x,y);
+ v.Pos = core::vector3df((f32)x,(f32)y,0.0f);
+ v.TCoords.X = (f32)letterWidth / (f32)x;
+ v.TCoords.Y = (f32)letterHeight / (f32)y;
+ v.Normal = core::vector3df(0,0,-1);
+ v.Color = SColor(255,255,255,255);
+ }
+ }
+ // clear index list
+ inds.clear();
+
+ // create each char in the font...
+ it = FontTool->CharMap.getIterator();
+ while(!it.atEnd())
+ {
+ addChar((*it).getKey());
+ it++;
+ }
+ }
+
+ ~CVectorFontTool()
+ {
+ if (triangulator)
+ delete triangulator;
+ }
+
+ void addChar(wchar_t thischar)
+ {
+ const s32 area = FontTool->CharMap[thischar];
+ const CFontTool::SFontArea &fa = FontTool->Areas[area];
+
+ const s32 img = fa.sourceimage;
+ const core::rect<s32>& r = fa.rectangle;
+
+ // init image memory
+ IImage *image = FontTool->currentImages[img];
+ for (u32 i=0; i < imagedata.size(); ++i)
+ imagedata[i] = false;
+ for (s32 y=r.UpperLeftCorner.Y; y < r.LowerRightCorner.Y; ++y)
+ {
+ for (s32 x=r.UpperLeftCorner.X; x < r.LowerRightCorner.X ; ++x)
+ if (image->getPixel(x,y).getBlue() > 0)
+ {
+ imagedata[letterWidth*(y-r.UpperLeftCorner.Y) +(x-r.UpperLeftCorner.X)] = true;
+ }
+ }
+
+ // get shape areas
+ triangulator = new CGroupFinder(imagedata.pointer(), letterWidth, letterHeight, FontTool->Device );
+
+ wprintf(L"Created character '%c' in texture %d\n", thischar, img );
+
+ //floodfill->drawEdges(FontTool->Device, 500, 3);
+ //floodfill->drawTriangles(FontTool->Device, 500,30);
+ //floodfill->drawTriLines(FontTool->Device, 200,3);
+
+ /*
+ if (area==32 && map == 0)
+ {
+ scene::ISceneManager *smgr = FontTool->Device->getSceneManager();
+ smgr->addCameraSceneNodeFPS();
+ while(FontTool->Device->run())
+ {
+ //floodfill->drawEdges(FontTool->Device, 100, 30);
+ FontTool->Device->getVideoDriver()->beginScene(true, true, video::SColor(0,200,200,200));
+ smgr->drawAll();
+ floodfill->drawTri3D(FontTool->Device, 100);
+ FontTool->Device->getVideoDriver()->endScene();
+ }
+ }*/
+
+ u32 lastind = triangles.indexes.size();
+
+ // loop through each shape and add it to the current character...
+ for (u32 i=0; i < triangulator->groups.size(); ++i)
+ triangles += triangulator->groups[i].triangles;
+
+ // add character details
+ charstarts.push_back(lastind);
+ charlengths.push_back(triangles.indexes.size() - lastind);
+ chars.push_back(thischar);
+ }
+
+ bool saveVectorFont(const c8 *filename, const c8 *formatname)
+ {
+ IrrlichtDevice *Device = FontTool->Device;
+
+ if (triangles.indexes.size() == 0)
+ {
+ Device->getLogger()->log("No vector data to write, aborting.");
+ return false;
+ }
+
+ core::stringc fn = filename;
+
+ if (core::stringc(formatname) == core::stringc("xml"))
+ {
+ fn += ".xml";
+ io::IXMLWriter *writer = FontTool->Device->getFileSystem()->createXMLWriter(fn.c_str());
+
+ // header and line breaks
+ writer->writeXMLHeader();
+ writer->writeLineBreak();
+
+ // write info header
+ writer->writeElement(L"font", false, L"type", L"vector");
+ writer->writeLineBreak();
+ writer->writeLineBreak();
+
+ // write each letter
+
+ for (u32 n=0; n<chars.size(); ++n)
+ {
+ u32 i = FontTool->CharMap[chars[n]];
+ CFontTool::SFontArea &fa = FontTool->Areas[i];
+ wchar_t c[2];
+ c[0] = chars[n];
+ c[1] = L'\0';
+ core::stringw area, under, over;
+ area = core::stringw(fa.rectangle.LowerRightCorner.X-
+ fa.rectangle.UpperLeftCorner.X);
+ area += L", ";
+ area += fa.rectangle.LowerRightCorner.Y-
+ fa.rectangle.UpperLeftCorner.Y;
+
+
+ core::array<core::stringw> names;
+ core::array<core::stringw> values;
+ names.clear();
+ values.clear();
+ // char
+ names.push_back(core::stringw(L"c"));
+ values.push_back(core::stringw(c));
+
+ // width+height
+ names.push_back(core::stringw(L"wh"));
+ values.push_back(area);
+
+ // start
+ names.push_back(core::stringw(L"st"));
+ values.push_back(core::stringw(charstarts[n]));
+ // length
+ names.push_back(core::stringw(L"len"));
+ values.push_back(core::stringw(charlengths[n]));
+
+ if (fa.underhang != 0)
+ {
+ under = core::stringw(fa.underhang);
+ names.push_back(core::stringw(L"u"));
+ values.push_back(under);
+ }
+ if (fa.overhang != 0)
+ {
+ over = core::stringw(fa.overhang);
+ names.push_back(core::stringw(L"o"));
+ values.push_back(over);
+ }
+ writer->writeElement(L"c", true, names, values);
+
+ writer->writeLineBreak();
+ }
+
+ // write vertex data
+ core::stringw data, count;
+ data = L"";
+ count = core::stringw(triangles.positions.size());
+ for (u32 i=0; i<triangles.positions.size(); ++i)
+ {
+ if (i!=0)
+ data += L", ";
+ data += (s32)triangles.positions[i].X;
+ data += L",";
+ data += (s32)triangles.positions[i].Y;
+ }
+ writer->writeElement(L"Vertices", true, L"count", count.c_str(), L"data", data.c_str());
+ writer->writeLineBreak();
+
+ // write index list
+ data = L"";
+ count = core::stringw(triangles.indexes.size());
+ for (u32 i=0; i<triangles.indexes.size(); i+=3)
+ {
+ if (i!=0)
+ data += L", ";
+ data += triangles.indexes[i+0];
+ data += L",";
+ data += triangles.indexes[i+1],
+ data += L",";
+ data += triangles.indexes[i+2];
+ }
+
+ writer->writeElement(L"Indices", true, L"count", count.c_str(), L"data", data.c_str());
+ writer->writeLineBreak();
+
+ writer->writeClosingTag(L"font");
+
+ writer->drop();
+
+ Device->getLogger()->log("Font saved.");
+ return true;
+
+ }
+ else if (core::stringc(formatname) == core::stringc("bin"))
+ {
+ FontTool->Device->getLogger()->log("binary fonts not supported yet, sorry");
+ return false;
+ }
+ else
+ {
+ FontTool->Device->getLogger()->log("unsupported file format, unable to save vector font");
+ return false;
+ }
+ }
+
+ S3DVertex& getVert(s32 x, s32 y) { return verts[letterWidth*y +x]; }
+
+ core::array<S3DVertex> verts;
+ core::array<u16> inds;
+ core::array<bool> imagedata;
+
+ core::array<s32> charstarts; // start position of each char
+ core::array<s32> charlengths; // index count
+ core::array<wchar_t> chars; // letters
+
+ CGroupFinder* triangulator;
+ CFontTool* FontTool;
+
+ s32 letterHeight;
+ s32 letterWidth;
+
+ STriangleList triangles;
+};
+
+#endif // __VECTOR_FONT_TOOL_INCLUDED__
+