Some refactoring to the Font class.

This commit is contained in:
Aloshi 2014-05-12 17:05:28 -05:00
parent b2193c3bf5
commit 4cf206d3eb
2 changed files with 104 additions and 144 deletions

View file

@ -7,11 +7,7 @@
#include "../Log.h" #include "../Log.h"
#include "../Util.h" #include "../Util.h"
FT_Library Font::sLibrary; FT_Library Font::sLibrary = NULL;
bool Font::libraryInitialized = false;
int Font::getDpiX() { return 96; }
int Font::getDpiY() { return 96; }
int Font::getSize() const { return mSize; } int Font::getSize() const { return mSize; }
@ -19,20 +15,20 @@ std::map< std::pair<std::string, int>, std::weak_ptr<Font> > Font::sFontMap;
void Font::initLibrary() void Font::initLibrary()
{ {
assert(sLibrary == NULL);
if(FT_Init_FreeType(&sLibrary)) if(FT_Init_FreeType(&sLibrary))
{ {
sLibrary = NULL;
LOG(LogError) << "Error initializing FreeType!"; LOG(LogError) << "Error initializing FreeType!";
}else{
libraryInitialized = true;
} }
} }
size_t Font::getMemUsage() const size_t Font::getMemUsage() const
{ {
if(!textureID) if(!mTextureID)
return 0; return 0;
return textureWidth * textureHeight * 4; return mTextureWidth * mTextureHeight * 4;
} }
size_t Font::getTotalMemUsage() size_t Font::getTotalMemUsage()
@ -55,7 +51,7 @@ size_t Font::getTotalMemUsage()
return total; return total;
} }
Font::Font(int size, const std::string& path) : fontScale(1.0f), mSize(size), mPath(path) Font::Font(int size, const std::string& path) : mFontScale(1.0f), mSize(size), mPath(path), mTextureID(0)
{ {
reload(ResourceManager::getInstance()); reload(ResourceManager::getInstance());
} }
@ -93,9 +89,11 @@ std::shared_ptr<Font> Font::get(int size, const std::string& path)
void Font::init(ResourceData data) void Font::init(ResourceData data)
{ {
if(!libraryInitialized) if(sLibrary == NULL)
initLibrary(); initLibrary();
deinit();
mMaxGlyphHeight = 0; mMaxGlyphHeight = 0;
buildAtlas(data); buildAtlas(data);
@ -103,51 +101,33 @@ void Font::init(ResourceData data)
void Font::deinit() void Font::deinit()
{ {
if(textureID) if(mTextureID)
{ {
glDeleteTextures(1, &textureID); glDeleteTextures(1, &mTextureID);
textureID = 0; mTextureID = 0;
} }
} }
void Font::buildAtlas(ResourceData data) void Font::buildAtlas(ResourceData data)
{ {
assert(mSize > 0);
FT_Face face;
if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face)) if(FT_New_Memory_Face(sLibrary, data.ptr.get(), data.length, 0, &face))
{ {
LOG(LogError) << "Error creating font face! (mPath: " << mPath << ", data.length: " << data.length << ")"; LOG(LogError) << "Error creating font face! (mPath: " << mPath << ", data.length: " << data.length << ")";
return; return;
} }
//FT_Set_Char_Size(face, 0, size * 64, getDpiX(), getDpiY());
FT_Set_Pixel_Sizes(face, 0, mSize); FT_Set_Pixel_Sizes(face, 0, mSize);
//find the size we should use // hardcoded texture size right now
FT_GlyphSlot g = face->glyph; mTextureWidth = 2048;
int w = 0; mTextureHeight = 512;
int h = 0;
/*for(int i = 32; i < 128; i++) // create the texture
{ glGenTextures(1, &mTextureID);
if(FT_Load_Char(face, i, FT_LOAD_RENDER)) glBindTexture(GL_TEXTURE_2D, mTextureID);
{
fprintf(stderr, "Loading character %c failed!\n", i);
continue;
}
w += g->bitmap.width;
h = std::max(h, g->bitmap.rows);
}*/
//the max size (GL_MAX_TEXTURE_SIZE) is like 3300
w = 2048;
h = 512;
textureWidth = w;
textureHeight = h;
//create the texture
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
@ -158,33 +138,19 @@ void Font::buildAtlas(ResourceData data)
glPixelStorei(GL_PACK_ALIGNMENT, 1); glPixelStorei(GL_PACK_ALIGNMENT, 1);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1); glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, w, h, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL); glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, mTextureWidth, mTextureHeight, 0, GL_ALPHA, GL_UNSIGNED_BYTE, NULL);
//copy the glyphs into the texture //copy the glyphs into the texture
int x = 0; int x = 0;
int y = 0; int y = 0;
int maxHeight = 0; int maxHeight = 0;
FT_GlyphSlot g = face->glyph;
for(int i = 32; i < 128; i++) for(int i = 32; i < 128; i++)
{ {
if(FT_Load_Char(face, i, FT_LOAD_RENDER)) if(FT_Load_Char(face, i, FT_LOAD_RENDER))
continue; continue;
//prints rendered texture to the console if(x + g->bitmap.width >= mTextureWidth)
/*std::cout << "uploading at x: " << x << ", w: " << g->bitmap.width << " h: " << g->bitmap.rows << "\n";
for(int k = 0; k < g->bitmap.rows; k++)
{
for(int j = 0; j < g->bitmap.width; j++)
{
if(g->bitmap.buffer[g->bitmap.width * k + j])
std::cout << ".";
else
std::cout << " ";
}
std::cout << "\n";
}*/
if(x + g->bitmap.width >= textureWidth)
{ {
x = 0; x = 0;
y += maxHeight + 1; //leave one pixel of space between glyphs y += maxHeight + 1; //leave one pixel of space between glyphs
@ -197,17 +163,17 @@ void Font::buildAtlas(ResourceData data)
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer); glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, g->bitmap.width, g->bitmap.rows, GL_ALPHA, GL_UNSIGNED_BYTE, g->bitmap.buffer);
charData[i].texX = x; mCharData[i].texX = x;
charData[i].texY = y; mCharData[i].texY = y;
charData[i].texW = g->bitmap.width; mCharData[i].texW = g->bitmap.width;
charData[i].texH = g->bitmap.rows; mCharData[i].texH = g->bitmap.rows;
charData[i].advX = (float)g->metrics.horiAdvance / 64.0f; mCharData[i].advX = (float)g->metrics.horiAdvance / 64.0f;
charData[i].advY = (float)g->metrics.vertAdvance / 64.0f; mCharData[i].advY = (float)g->metrics.vertAdvance / 64.0f;
charData[i].bearingX = (float)g->metrics.horiBearingX / 64.0f; mCharData[i].bearingX = (float)g->metrics.horiBearingX / 64.0f;
charData[i].bearingY = (float)g->metrics.horiBearingY / 64.0f; mCharData[i].bearingY = (float)g->metrics.horiBearingY / 64.0f;
if(charData[i].texH > mMaxGlyphHeight) if(mCharData[i].texH > mMaxGlyphHeight)
mMaxGlyphHeight = charData[i].texH; mMaxGlyphHeight = mCharData[i].texH;
x += g->bitmap.width + 1; //leave one pixel of space between glyphs x += g->bitmap.width + 1; //leave one pixel of space between glyphs
} }
@ -216,13 +182,13 @@ void Font::buildAtlas(ResourceData data)
FT_Done_Face(face); FT_Done_Face(face);
if((y + maxHeight) >= textureHeight) if((y + maxHeight) >= mTextureHeight)
{ {
//failed to create a proper font texture //failed to create a proper font texture
LOG(LogWarning) << "Font \"" << mPath << "\" with size " << mSize << " exceeded max texture size! Trying again..."; LOG(LogWarning) << "Font \"" << mPath << "\" with size " << mSize << " exceeded max texture size! Trying again...";
//try a 3/4th smaller size and redo initialization //try a 3/4th smaller size and redo initialization
fontScale *= 1.25f; mFontScale *= 1.25f;
mSize = (int)(mSize * (1.0f / fontScale)); mSize = (int)(mSize * (1.0f / mFontScale));
deinit(); deinit();
init(data); init(data);
} }
@ -230,7 +196,7 @@ void Font::buildAtlas(ResourceData data)
void Font::renderTextCache(TextCache* cache) void Font::renderTextCache(TextCache* cache)
{ {
if(!textureID) if(!mTextureID)
{ {
LOG(LogError) << "Error - tried to draw with Font that has no texture loaded!"; LOG(LogError) << "Error - tried to draw with Font that has no texture loaded!";
return; return;
@ -242,7 +208,7 @@ void Font::renderTextCache(TextCache* cache)
return; return;
} }
glBindTexture(GL_TEXTURE_2D, textureID); glBindTexture(GL_TEXTURE_2D, mTextureID);
glEnable(GL_TEXTURE_2D); glEnable(GL_TEXTURE_2D);
glEnable(GL_BLEND); glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
@ -265,12 +231,12 @@ void Font::renderTextCache(TextCache* cache)
glDisable(GL_BLEND); glDisable(GL_BLEND);
} }
Eigen::Vector2f Font::sizeText(std::string text) const Eigen::Vector2f Font::sizeText(std::string text, float lineSpacing) const
{ {
float lineWidth = 0.0f; float lineWidth = 0.0f;
float highestWidth = 0.0f; float highestWidth = 0.0f;
float y = getHeight(); float y = getHeight(lineSpacing);
for(unsigned int i = 0; i < text.length(); i++) for(unsigned int i = 0; i < text.length(); i++)
{ {
@ -282,13 +248,13 @@ Eigen::Vector2f Font::sizeText(std::string text) const
highestWidth = lineWidth; highestWidth = lineWidth;
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(); y += getHeight(lineSpacing);
} }
if(letter < 32 || letter >= 128) if(letter < 32 || letter >= 128)
letter = 127; letter = 127;
lineWidth += charData[letter].advX * fontScale; lineWidth += mCharData[letter].advX * mFontScale;
} }
if(lineWidth > highestWidth) if(lineWidth > highestWidth)
@ -297,14 +263,14 @@ Eigen::Vector2f Font::sizeText(std::string text) const
return Eigen::Vector2f(highestWidth, y); return Eigen::Vector2f(highestWidth, y);
} }
float Font::getHeight() const float Font::getHeight(float lineSpacing) const
{ {
return mMaxGlyphHeight * 1.5f * fontScale; return mMaxGlyphHeight * lineSpacing * mFontScale;
} }
float Font::getLetterHeight() const float Font::getLetterHeight() const
{ {
return charData['S'].texH * fontScale; return mCharData['S'].texH * mFontScale;
} }
//the worst algorithm ever written //the worst algorithm ever written
@ -368,13 +334,13 @@ std::string Font::wrapText(std::string text, float xLen) const
return out; return out;
} }
Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen) const Eigen::Vector2f Font::sizeWrappedText(std::string text, float xLen, float lineSpacing) const
{ {
text = wrapText(text, xLen); text = wrapText(text, xLen);
return sizeText(text); return sizeText(text, lineSpacing);
} }
Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, int cursor) const Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, int cursor, float lineSpacing) const
{ {
std::string wrappedText = wrapText(text, xLen); std::string wrappedText = wrapText(text, xLen);
@ -393,7 +359,7 @@ Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, i
//this is where the wordwrap inserted a newline //this is where the wordwrap inserted a newline
//reset lineWidth and increment y, but don't consume a cursor character //reset lineWidth and increment y, but don't consume a cursor character
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(); y += getHeight(lineSpacing);
wrapOffset++; wrapOffset++;
i--; i--;
@ -403,14 +369,14 @@ Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, i
if(letter == '\n') if(letter == '\n')
{ {
lineWidth = 0.0f; lineWidth = 0.0f;
y += getHeight(); y += getHeight(lineSpacing);
continue; continue;
} }
if(letter < 32 || letter >= 128) if(letter < 32 || letter >= 128)
letter = 127; letter = 127;
lineWidth += charData[letter].advX * fontScale; lineWidth += mCharData[letter].advX * mFontScale;
} }
return Eigen::Vector2f(lineWidth, y); return Eigen::Vector2f(lineWidth, y);
@ -420,9 +386,9 @@ Eigen::Vector2f Font::getWrappedTextCursorOffset(std::string text, float xLen, i
//TextCache //TextCache
//============================================================================================================= //=============================================================================================================
TextCache* Font::buildWrappedTextCache(const std::string& text, const Eigen::Vector2f& offset, float xLen, Alignment alignment, unsigned int color) TextCache* Font::buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment, float lineSpacing)
{ {
if(!textureID) if(!mTextureID)
{ {
LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!"; LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!";
return NULL; return NULL;
@ -430,12 +396,14 @@ TextCache* Font::buildWrappedTextCache(const std::string& text, const Eigen::Vec
// todo // todo
return NULL; return NULL;
} }
TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color) TextCache* Font::buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color)
{ {
if(!textureID) if(!mTextureID)
{ {
LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!"; LOG(LogError) << "Error - tried to build TextCache with Font that has no texture loaded!";
return NULL; return NULL;
@ -446,13 +414,18 @@ TextCache* Font::buildTextCache(const std::string& text, float offsetX, float of
TextCache::Vertex* vert = new TextCache::Vertex[vertCount]; TextCache::Vertex* vert = new TextCache::Vertex[vertCount];
GLubyte* colors = new GLubyte[vertCount * 4]; GLubyte* colors = new GLubyte[vertCount * 4];
// all glyph sizes/texture offsets are in pixels,
// so the only rounding we have to worry about is the offset
offsetX = round(offsetX);
offsetY = round(offsetY);
//texture atlas width/height //texture atlas width/height
float tw = (float)textureWidth; float tw = (float)mTextureWidth;
float th = (float)textureHeight; float th = (float)mTextureHeight;
float x = offsetX; float x = offsetX;
float yTop = charData['S'].bearingY * fontScale; float yTop = mCharData['S'].bearingY * mFontScale;
float yBot = getHeight(); float yBot = getHeight();
float y = offsetY + (yBot + yTop)/2.0f; float y = offsetY + (yBot + yTop)/2.0f;
@ -473,14 +446,14 @@ TextCache* Font::buildTextCache(const std::string& text, float offsetX, float of
letter = 127; //print [X] if character is not standard ASCII letter = 127; //print [X] if character is not standard ASCII
//the glyph might not start at the cursor position, but needs to be shifted a bit //the glyph might not start at the cursor position, but needs to be shifted a bit
const float glyphStartX = x + charData[letter].bearingX * fontScale; const float glyphStartX = x + mCharData[letter].bearingX * mFontScale;
//order is bottom left, top right, top left //order is bottom left, top right, top left
vert[i + 0].pos << glyphStartX, y + (charData[letter].texH - charData[letter].bearingY) * fontScale; vert[i + 0].pos << glyphStartX, y + (mCharData[letter].texH - mCharData[letter].bearingY) * mFontScale;
vert[i + 1].pos << glyphStartX + charData[letter].texW * fontScale, y - charData[letter].bearingY * fontScale; vert[i + 1].pos << glyphStartX + mCharData[letter].texW * mFontScale, y - mCharData[letter].bearingY * mFontScale;
vert[i + 2].pos << glyphStartX, vert[i + 1].pos.y(); vert[i + 2].pos << glyphStartX, vert[i + 1].pos.y();
Eigen::Vector2i charTexCoord(charData[letter].texX, charData[letter].texY); Eigen::Vector2i charTexCoord(mCharData[letter].texX, mCharData[letter].texY);
Eigen::Vector2i charTexSize(charData[letter].texW, charData[letter].texH); Eigen::Vector2i charTexSize(mCharData[letter].texW, mCharData[letter].texH);
vert[i + 0].tex << charTexCoord.x() / tw, (charTexCoord.y() + charTexSize.y()) / th; vert[i + 0].tex << charTexCoord.x() / tw, (charTexCoord.y() + charTexSize.y()) / th;
vert[i + 1].tex << (charTexCoord.x() + charTexSize.x()) / tw, charTexCoord.y() / th; vert[i + 1].tex << (charTexCoord.x() + charTexSize.x()) / tw, charTexCoord.y() / th;
@ -497,13 +470,7 @@ TextCache* Font::buildTextCache(const std::string& text, float offsetX, float of
vert[i + 5].tex[0] = vert[i + 1].tex.x(); vert[i + 5].tex[0] = vert[i + 1].tex.x();
vert[i + 5].tex[1] = vert[i + 0].tex.y(); vert[i + 5].tex[1] = vert[i + 0].tex.y();
// round x += mCharData[letter].advX * mFontScale;
for(int j = 0; j < 6; j++)
{
vert[i + j].pos = roundVector(vert[i + j].pos);
}
x += charData[letter].advX * fontScale;
} }
TextCache::CacheMetrics metrics = { sizeText(text) }; TextCache::CacheMetrics metrics = { sizeText(text) };

View file

@ -1,5 +1,4 @@
#ifndef _FONT_H_ #pragma once
#define _FONT_H_
#include <string> #include <string>
#include "../platform.h" #include "../platform.h"
@ -35,38 +34,18 @@ public:
static std::shared_ptr<Font> get(int size, const std::string& path = getDefaultPath()); static std::shared_ptr<Font> get(int size, const std::string& path = getDefaultPath());
~Font(); virtual ~Font();
FT_Face face; Eigen::Vector2f sizeText(std::string text, float lineSpacing = 1.5f) const; // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis.
//contains sizing information for every glyph.
struct charPosData {
int texX;
int texY;
int texW;
int texH;
float advX; //!<The horizontal distance to advance to the next character after this one
float advY; //!<The vertical distance to advance to the next character after this one
float bearingX; //!<The horizontal distance from the cursor to the start of the character
float bearingY; //!<The vertical distance from the cursor to the start of the character
};
charPosData charData[128];
GLuint textureID;
Eigen::Vector2f sizeText(std::string text) const; // Returns the expected size of a string when rendered. Extra spacing is applied to the Y axis.
TextCache* buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color); TextCache* buildTextCache(const std::string& text, float offsetX, float offsetY, unsigned int color);
TextCache* buildWrappedTextCache(const std::string& text, const Eigen::Vector2f& offset, float xLen, Alignment alignment, unsigned int color); TextCache* buildTextCache(const std::string& text, Eigen::Vector2f offset, unsigned int color, float xLen, Alignment alignment = ALIGN_LEFT, float lineSpacing = 1.5f);
void renderTextCache(TextCache* cache); void renderTextCache(TextCache* cache);
std::string wrapText(std::string text, float xLen) const; // Inserts newlines into text to make it wrap properly. std::string wrapText(std::string text, float xLen) const; // Inserts newlines into text to make it wrap properly.
Eigen::Vector2f sizeWrappedText(std::string text, float xLen) const; // Returns the expected size of a string after wrapping is applied. Eigen::Vector2f sizeWrappedText(std::string text, float xLen, float lineSpacing = 1.5f) const; // Returns the expected size of a string after wrapping is applied.
Eigen::Vector2f getWrappedTextCursorOffset(std::string text, float xLen, int cursor) const; // Returns the position of of the cursor after moving "cursor" characters. Eigen::Vector2f getWrappedTextCursorOffset(std::string text, float xLen, int cursor, float lineSpacing = 1.5f) const; // Returns the position of of the cursor after moving "cursor" characters.
float getHeight() const; float getHeight(float lineSpacing = 1.5f) const;
float getLetterHeight() const; float getLetterHeight() const;
void unload(std::shared_ptr<ResourceManager>& rm) override; void unload(std::shared_ptr<ResourceManager>& rm) override;
@ -83,12 +62,7 @@ public:
static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by font textures (in bytes) static size_t getTotalMemUsage(); // returns an approximation of total VRAM used by font textures (in bytes)
private: private:
static int getDpiX();
static int getDpiY();
static FT_Library sLibrary; static FT_Library sLibrary;
static bool libraryInitialized;
static std::map< std::pair<std::string, int>, std::weak_ptr<Font> > sFontMap; static std::map< std::pair<std::string, int>, std::weak_ptr<Font> > sFontMap;
Font(int size, const std::string& path); Font(int size, const std::string& path);
@ -98,18 +72,39 @@ private:
void buildAtlas(ResourceData data); //Builds a "texture atlas," one big OpenGL texture with glyphs 32 to 128. void buildAtlas(ResourceData data); //Builds a "texture atlas," one big OpenGL texture with glyphs 32 to 128.
int textureWidth; //OpenGL texture width //contains sizing information for every glyph.
int textureHeight; //OpenGL texture height struct CharData
{
int texX;
int texY;
int texW;
int texH;
float advX; //!<The horizontal distance to advance to the next character after this one
float advY; //!<The vertical distance to advance to the next character after this one
float bearingX; //!<The horizontal distance from the cursor to the start of the character
float bearingY; //!<The vertical distance from the cursor to the start of the character
};
CharData mCharData[128];
GLuint mTextureID;
int mTextureWidth; //OpenGL texture width
int mTextureHeight; //OpenGL texture height
int mMaxGlyphHeight; int mMaxGlyphHeight;
float fontScale; //!<Font scale factor. It is > 1.0 if the font would be to big for the texture float mFontScale; //!<Font scale factor. It is > 1.0 if the font would be to big for the texture
int mSize; int mSize;
const std::string mPath; const std::string mPath;
friend TextCache;
}; };
// Used to store a sort of "pre-rendered" string. // Used to store a sort of "pre-rendered" string.
// When a TextCache is constructed (Font::buildTextCache()), the vertices and texture coordinates of the string are calculated and stored in the TextCache object. // When a TextCache is constructed (Font::buildTextCache()), the vertices and texture coordinates of the string are calculated and stored in the TextCache object.
// Rendering a TextCache (Font::renderTextCache) every frame is MUCH faster than calling Font::drawText() and its variants. // Rendering a previously constructed TextCache (Font::renderTextCache) every frame is MUCH faster than rebuilding one every frame.
// Keep in mind you still need the Font object to render a TextCache (as the Font holds the OpenGL texture), and if a Font changes your TextCache may become invalid. // Keep in mind you still need the Font object to render a TextCache (as the Font holds the OpenGL texture), and if a Font changes your TextCache may become invalid.
class TextCache class TextCache
{ {
@ -134,5 +129,3 @@ public:
Vertex* verts; Vertex* verts;
GLubyte* colors; GLubyte* colors;
}; };
#endif