sunLoadingImage
whowedImag
decoration left 1
decoration left 2
transhome
transprojects
transgallery
transarticles
decoration rigth
English
Українська
Show/Hide search bar
black cat logo variable logo
[26 Січ 2013]

Тріангуляція контурів True Type символа з допомогою бібліотеки GLU

У попередніх уроках розглядалося як отримати контури для символа True Type шрифта, а також як правильно сформувати рядок тексту з окремих символів. Контури символів можна рендерити лініями, але не заповненими фігурами (як показано на наступному малюнку). Для того, щоб рендерити текст заповненим, необхідно провести тріангуляцію контурів для кожного символа. При цьому процес формування рядка з окремих символів залишається без змін.
Відрендерені тріангульовані контури для символів виглядять так:
Для тріангуляції контурів можна використати бібліотеку GLU. Вона добре підходить для тріангуляції опуклих і вгнутих полігонів, а також полігонів з отворами. Процес тріангуляції починається зі створення GLUtesselator і його ініціалізації. Далі у об'єкт додається полігон - в даному випадку кожен полігон представляє собою символ. Полігони складаються з контурів. Контури ж складаються з окремих вершин, по яким буде проводитися тріангуляція. Потім викликається тріангуляція, і GLUtesselator починає повертати результати.
Включіть файл glu32.lib в project dependencies. А також необхідно включити наступні заголовки:
#include <Windows.h>
#include "gl\glu.h"
Функція triangulateSymbolContours() проводить тріангуляцію контурів символів. Спочатку ініціалізується об'єкт для тріангуляції і заповнюється окремими контурами. Коли вся інформація про контури збережена, то викликається тріангуляція. Результат представляє з себе масив вершин. Кожні три вершини формують трикутник.
void triangulateSymbolContours(
      const std::vector<std::vector<glm::vec2>> & contours,
      std::vector<glm::vec2> & vertices)
{
   // провести тріангуляцію, тільки якщо є контури
   if(contours.size()>0)
   {
      Triangulator triangulator;
      triangulator.beginTriangulation();

      // додати кожен контур в тріангулятор
      for(uint i=0; i<contours.size(); i++)
      {
         // дадавати тільки не пусті контура
         if(contours[i].size()>0)
         {
            triangulator.beginContour();
            // додати кожну вершину в тріангулятор
            for(uint j=0; j<contours[i].size(); j+=2)
            {
               triangulator.addVertex(glm::vec2(contours[i][j], contours[i][j+1]));
            }
            triangulator.endContour();
         }
      }
      // end initialization and begin triangulation
      triangulator.endTriangulation();

      // return results of triangulation
      vertices = triangulator.results();
   }
}
Допоміжний клас для тріангуляції:
class Triangulator
{
public:
   // Конструктор
   Triangulator();
   // Дуструктор
   ~Triangulator();

   // Почати ініціалізацію тріангуляції
   void beginTriangulation();
   // Розпочати новий контур
   void beginContour();
   // додати вершину у поточний контур
   void addVertex(const glm::vec2 & vertex);
   // закінчити поточний контур
   void endContour();
   // завершити ініціалізацію і тріангулювати записані дані
   void endTriangulation();

   // повернути результати (кожні три вершини - трикутник)
   std::vector<glm::vec2> results() const;

private:
   GLUtesselator * triangulator;
   std::unique_ptr<TessContext> context;
};
Імплементація допоміжного класу для тріангуляції:
// оголошення CALLBACK необхідне для GLU бібліотеки
#ifndef CALLBACK
#define CALLBACK __stdcall
#endif

// допоміжна структура у якої є доступ до GLU тріангулятора (контекст тріангулятора)
struct TessContext
{
   // Тимчасовий список для збереження вхідних даних
   std::list<std::unique_ptr<GLdouble[]>> input;
   // Масив індексів вершин (кожні три утворюють трикутник)
   std::vector<uint> indices;
   // Масив вершин
   std::vector<float> result;

   // поточні параметри контексту тріангуляції
   GLenum currentPrimitiveType;
   uint currentIndex;
   bool isFirstVertex;
   uint firstVertexIndex;
   bool isSecondVertex;
   uint secondVertexIndex;

   TessContext()
      : currentPrimitiveType(GL_NONE)
      , currentIndex(0)
      , isFirstVertex(false)
      , isSecondVertex(false)
   {
   }
};

// конструктор
Triangulator::Triangulator()
   : triangulator(nullptr)
{
}

// деструктор
Triangulator::~Triangulator()
{
   // видалити GLU тріангулятор
   if(triangulator)
   {
      gluDeleteTess(triangulator);
   }
}

// функція, що викликається, коли тріангулятор починає масив примітивів
void CALLBACK tessBegin(GLenum type, TessContext * ctx);
// функція, що викликається, коли тріангулятор продукує вершину
void CALLBACK tessVertex(GLvoid *, TessContext * ctx);

// PS: попередні функції мають аналогічну поведінку як immediate mode функції glVertex, glBegin, glEnd зі старших версій OpenGL

// Ініціалізує тріангуляцію
void Triangulator::beginTriangulation()
{
   // створити новий обєкт тріангуляції
   if(triangulator)
   {
      gluDeleteTess(triangulator);
   }
   triangulator = gluNewTess();

   // встановити колбек функції для тріангулятора
   gluTessCallback(triangulator, GLU_TESS_BEGIN_DATA,
                  (void (CALLBACK*)())tessBegin);
   gluTessCallback(triangulator, GLU_TESS_VERTEX_DATA,
                   (void (CALLBACK*)())tessVertex);

   // встановити нормаль по замовчуванню.
   // Спрямувати вздовж осі Z, так як символи на площині XY
   gluTessNormal(triangulator, 0, 0, 1);

   // створити і встановити контекст для тріангулятора -
   // обєкт до якого є доступ у колбек функціях
   context = std::unique_ptr<TessContext>(new TessContext);
   gluTessBeginPolygon(triangulator, context.get());
}

// розпочати новий контур
void Triangulator::beginContour()
{
   gluTessBeginContour(triangulator);
}

// додати нову вершину в контур
void Triangulator::addVertex(const glm::vec2 & vertex)
{
   GLdouble * tInput = new GLdouble[3];
   tInput[0] = vertex.x;
   tInput[1] = vertex.y;
   tInput[2] = 0;
   context->input.push_back(std::unique_ptr<GLdouble[]>(tInput));

   gluTessVertex(triangulator, tInput, tInput);
}

// завершити контур
void Triangulator::endContour()
{
   gluTessEndContour(triangulator);
}

// завершити тріангуляцію і видалити тріангулятор
void Triangulator::endTriangulation()
{
   gluTessEndPolygon(triangulator);

   if(triangulator)
   {
      gluDeleteTess(triangulator);
      triangulator = nullptr;
   }
}

// повернути результати тріангуляції
std::vector<glm::vec2> Triangulator::results() const
{
   std::vector<glm::vec2> out;

   std::vector<float> & res = context->result;
   uint num = context->indices.size();

   // вибрати кожну вершину з допомогою індекса
   for(uint i=0; i<num; i++)
   {
      uint index = context->indices[i];
      out.push_back(glm::vec2(res[index*2], res[index*2+1]));
   }

   return std::move(out);
}

// викликається при початку нового типу примітивів
// встановлює в контексті тип примітива, і те, що ні першої,
// ні другої вершини трикутника ще не було
void CALLBACK tessBegin(GLenum type, TessContext * ctx)
{
   ctx->currentPrimitiveType = type;
   ctx->isFirstVertex = false;
   ctx->isSecondVertex = false;
}

// викликається, коли тріангулятор додає нову вершину в рузультат
void CALLBACK tessVertex(GLvoid * data, TessContext * ctx)
{
   // вершина, що додається до результату
   GLdouble * coord = (GLdouble*)data;
   ctx->result.push_back((float)coord[0]);
   ctx->result.push_back((float)coord[1]);

   // поведінка в залежності від типу примітива
   // потрібно правильно сформувати масив індексів
   if(ctx->currentPrimitiveType == GL_TRIANGLES)
   {
      ctx->indices.push_back(ctx->currentIndex++);
   }
   else if(ctx->currentPrimitiveType == GL_TRIANGLE_FAN)
   {
      if(ctx->isSecondVertex)
      {
         // новий трикутник
         ctx->indices.push_back(ctx->firstVertexIndex);
         ctx->indices.push_back(ctx->secondVertexIndex);
         ctx->indices.push_back(ctx->currentIndex);

         // індекс наступної вершини
         ctx->secondVertexIndex = ctx->currentIndex++;
      }
      else if(ctx->isFirstVertex)
      {
         // друга вершина в трикутнику
         ctx->isSecondVertex = true;
         ctx->secondVertexIndex = ctx->currentIndex++;
      }
      else
      {
         // перша вершина в трикутнику
         ctx->isFirstVertex = true;
         ctx->firstVertexIndex = ctx->currentIndex++;
      }
   }
   else if(ctx->currentPrimitiveType == GL_TRIANGLE_STRIP)
   {
      if(ctx->isSecondVertex)
      {
         // новий трикутник
         ctx->indices.push_back(ctx->firstVertexIndex);
         ctx->indices.push_back(ctx->secondVertexIndex);
         ctx->indices.push_back(ctx->currentIndex);

         // до наступної вершини
         ctx->firstVertexIndex = ctx->secondVertexIndex;
         ctx->secondVertexIndex = ctx->currentIndex++;
      }
      else if(ctx->isFirstVertex)
      {
         // друга вершина в трикутнику
         ctx->isSecondVertex = true;
         ctx->secondVertexIndex = ctx->currentIndex++;
      }
      else
      {
         // перша вершина в трикутнику
         ctx->isFirstVertex = true;
         ctx->firstVertexIndex = ctx->currentIndex++;
      }
   }
}



Sun and Black Cat- Ігор Дихта (igor dykhta email) © 2007-2014