
| main.cpp |

// **************************************************************************
// *** Right-Triangle Irregular Network (RTIN) Toy
// *** (c) Bernie Freidin, 2000
// *** Requires OPENGL32, GLU32, GLUT32
// **************************************************************************
// **************************************************************************
// *** Keys:
// *** 1,2,3,4,5: reset mesh
// *** 6,7,8,9: set color mode
// *** A: set texture mode A
// *** B: set texture mode B
// *** ESCAPE: quit
// **************************************************************************
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gl/glut.h>
// **************************************************************************
static int VIEW_X = 512;
static int VIEW_Y = 512;
static int CONTINUOUS_SPLIT = 1;
static int COLOR_MODE = 2;
#define MAX_DEPTH 12
#define MAX_NODES 65000
#define ALPHA 1.0
#define DEBUGGING 0
// **************************************************************************
#define SQRT3_2 0.866025403784438646763723170752936
#if DEBUGGING
static void ASSERT(int cond)
{
if(!cond)
{
FILE *err = fopen("err.log", "w");
if(!err) exit(-2); // wtf?
fprintf(err, "assertion failed!\n");
fclose(err);
exit(-1);
}
}
#else
#define ASSERT(cond)
#endif
struct node_t
{
// nodes form both a connected mesh and a binary tree
double x[3], y[3];
int type, flip, value, depth;
node_t *child[2];
node_t *parent;
node_t *adjacent[3];
};
struct rules_t
{
// rules define the splitting of node triangles
int c_type[2];
int c_flip[2];
int c_data[12];
int rot;
};
#define MARKED ((node_t*)-1)
static node_t *MESH = NULL, *MP = NULL;
static node_t *HILITE = NULL;
static int HLT__K = 0, VALUE = 0;
static int MESH_TREES = 0;
static double COLOR_TABLE[(MAX_DEPTH+1)*3];
#define NUM_NODE_TYPES 5
static rules_t RULES[NUM_NODE_TYPES] = {
{{0,0}, {0,0}, {1,2,0, 1,2,0, 2,0,1, 2,0,1}, 0}, // 4.8.8
{{2,3}, {0,0}, {1,2,0, 1,2,0, 0,1,2, 0,1,2}, 0}, // 4.6.12
{{1,1}, {1,0}, {0,2,1, 2,1,0, 2,0,1, 2,0,1}, 0}, // 3.12.12
{{1,1}, {0,1}, {1,2,0, 1,2,0, 2,1,0, 1,0,2}, 1}, // 6.6.6
{{4,4}, {0,0}, {0,1,2, 0,1,2, 0,1,2, 0,1,2}, 1} // freeform
};
static node_t *FindAdjacentChild(node_t *n, node_t *m);
static void NodeLink1(node_t *n, int which);
static void NodeLink2(node_t *n, int which);
static void NodeCycle(node_t *n, int data[6]);
static void NodeSplit(node_t *n, int value);
static node_t *NodePick(double x, double y, int *k);
static void NodeDraw(node_t *n, int wire, int k);
static void MeshDraw(node_t *n);
static node_t *FindAdjacentChild(node_t *n, node_t *m)
{
for(int j = 0; j < 2; j++) { if(!n->child[j]) continue;
for(int i = 0; i < 3; i++) {
if(n->child[j]->adjacent[i] == m) return n->child[j];
}}
return NULL;
}
static void NodeLink1(node_t *n, int which)
{
node_t *n0 = n->adjacent[which];
for(int i = 0; i < 3; i++) {
if(n0->adjacent[i] == n->parent) {
n0->adjacent[i] = n;
return;
}}
}
static void NodeLink2(node_t *n, int which)
{
node_t *n0 = n->adjacent[which];
for(int i = 0; i < 3; i++) {
if(n0->adjacent[i] == MARKED) {
n0->adjacent[i] = n;
return;
}}
}
static void NodeCycle(node_t *n, int data[6])
{
node_t tmp;
memcpy(&tmp, n, sizeof(node_t));
for(int i = 0; i < 3; i++)
{
n->x[data[i]] = tmp.x[i];
n->y[data[i]] = tmp.y[i];
n->adjacent[data[i+3]] = tmp.adjacent[i];
}
}
static void NodeSplit(node_t *n, node_t *m, int value)
{
ASSERT(n->child[0] == NULL);
ASSERT(n->child[1] == NULL);
ASSERT(MP-MESH+2 <= MAX_NODES);
// allocate child nodes
node_t *c1 = MP++;
node_t *c2 = MP++;
// freely oriented?
rules_t *rules = &RULES[n->type];
if(rules->rot && m)
{
static int ROT1[] = {1,2,0,1,2,0};
while(n->adjacent[0] != m) NodeCycle(n, ROT1);
}
// set vertices
double midpt_x = (n->x[0] + n->x[1])/2.0;
double midpt_y = (n->y[0] + n->y[1])/2.0;
c1->x[0] = n->x[0], c1->y[0] = n->y[0];
c1->x[1] = midpt_x, c1->y[1] = midpt_y;
c1->x[2] = n->x[2], c1->y[2] = n->y[2];
c2->x[0] = midpt_x, c2->y[0] = midpt_y;
c2->x[1] = n->x[1], c2->y[1] = n->y[1];
c2->x[2] = n->x[2], c2->y[2] = n->y[2];
// set parent-child pointers
c1->parent = n;
c2->parent = n;
c1->child[0] = c1->child[1] = NULL;
c2->child[0] = c2->child[1] = NULL;
n->child[0] = c1;
n->child[1] = c2;
// set adjacent pointers
c1->adjacent[1] = c2;
c1->adjacent[2] = n->adjacent[2];
c2->adjacent[1] = n->adjacent[1];
c2->adjacent[2] = c1;
if(c1->adjacent[2]) NodeLink1(c1, 2);
if(c2->adjacent[1]) NodeLink1(c2, 1);
// set node values
c1->type = rules->c_type[0];
c2->type = rules->c_type[1];
c1->flip = rules->c_flip[0]^n->flip;
c2->flip = rules->c_flip[1]^n->flip;
c1->value = value;
c2->value = value;
int depth = n->depth+1;
if(depth > MAX_DEPTH) depth = MAX_DEPTH;
c1->depth = depth;
c2->depth = depth;
// recursive split propagation
if(n->adjacent[0])
{
node_t *a0, *a1 = n->adjacent[0];
if(a1->child[0] || a1->child[1])
{
// mark children for later linking
c1->adjacent[0] = MARKED;
c2->adjacent[0] = MARKED;
}
else
{
// keep splitting until T-vertex is gone
while(a1)
{
NodeSplit(a1, n, value);
a0 = a1;
a1 = FindAdjacentChild(a0, n);
}
// link children to marked children
int sw = n->flip ^ a0->flip;
c1->adjacent[0] = a0->child[sw^1];
c2->adjacent[0] = a0->child[sw];
// note - if either of these is false,
// then our triangulation is corrupt
if(c1->adjacent[0]) NodeLink2(c1, 0);
if(c2->adjacent[0]) NodeLink2(c2, 0);
}
}
else
{
// edge of mesh
c1->adjacent[0] = NULL;
c2->adjacent[0] = NULL;
}
// re-orient children
NodeCycle(c1, rules->c_data + 0);
NodeCycle(c2, rules->c_data + 6);
// remove adjacent pointers
n->adjacent[0] = NULL;
n->adjacent[1] = NULL;
n->adjacent[2] = NULL;
}
static node_t *NodePick(double x, double y, int *k)
{
node_t *n = MESH;
// first, determine initial triangle
for(int i = 0; i <= MESH_TREES; i++, n++)
{
if(i == MESH_TREES) return NULL;
if((n->x[0]-n->x[1])*(n->y[0]-y) <
(n->y[0]-n->y[1])*(n->x[0]-x) ) continue;
if((n->x[1]-n->x[2])*(n->y[1]-y) <
(n->y[1]-n->y[2])*(n->x[1]-x) ) continue;
if((n->x[2]-n->x[0])*(n->y[2]-y) <
(n->y[2]-n->y[0])*(n->x[2]-x) ) continue;
break;
}
// traverse binary tree
while(1)
{
if(!n || !n->child[0] || !n->child[1]) break;
double ax = n->x[2] - (n->x[0] + n->x[1])/2.0;
double ay = n->y[2] - (n->y[0] + n->y[1])/2.0;
double bx = n->x[2] - x;
double by = n->y[2] - y;
n = n->child[(ax*by < ay*bx) ? n->flip : n->flip^1];
}
if(n && k)
{
if(!RULES[n->type].rot) *k = 2;
else
{
double ax = n->x[2] - (n->x[0] + n->x[1])/2.0;
double ay = n->y[2] - (n->y[0] + n->y[1])/2.0;
double bx = n->x[2] - x;
double by = n->y[2] - y;
int f1 = (ax*by < ay*bx) ? n->flip^1 : n->flip;
ax = n->x[0] - (n->x[1] + n->x[2])/2.0;
ay = n->y[0] - (n->y[1] + n->y[2])/2.0;
bx = n->x[0] - x;
by = n->y[0] - y;
int f2 = (ax*by < ay*bx) ? n->flip^1 : n->flip;
ax = n->x[1] - (n->x[2] + n->x[0])/2.0;
ay = n->y[1] - (n->y[2] + n->y[0])/2.0;
bx = n->x[1] - x;
by = n->y[1] - y;
int f3 = (ax*by < ay*bx) ? n->flip^1 : n->flip;
if(f1 && !f3) *k = 0; else
if(f2 && !f1) *k = 1; else
if(f3 && !f2) *k = 2;
}
}
return n;
}
static void NodeDraw(node_t *n, int wire, int k)
{
if(wire)
{
glDisable(GL_TEXTURE_2D);
glBegin(GL_LINE_LOOP);
glVertex2d(n->x[0], n->y[0]);
glVertex2d(n->x[1], n->y[1]);
glVertex2d(n->x[2], n->y[2]);
glEnd();
if(k >= 0)
{
glBegin(GL_LINES);
glVertex2d(n->x[k], n->y[k]);
glVertex2d((n->x[(k+1)%3] + n->x[(k+2)%3])/2.0,
(n->y[(k+1)%3] + n->y[(k+2)%3])/2.0);
glEnd();
}
glEnable(GL_TEXTURE_2D);
}
else
{
double *c;
switch(COLOR_MODE)
{
case 0: // mono gray
glColor4d(1.0, 1.0, 1.0, ALPHA);
break;
case 1: // turn-based gray
if(n->value&1) glColor4d(0.5, 0.5, 0.5, ALPHA);
else glColor4d(1.0, 1.0, 1.0, ALPHA);
break;
case 2: // depth-based color
c = COLOR_TABLE + n->depth*3;
glColor4d(c[0], c[1], c[2], ALPHA);
break;
case 3: // type-based color
switch(n->type)
{
case 0: glColor4d(1.0, 1.0, 0.0, ALPHA); break;
case 1: glColor4d(0.0, 1.0, 0.0, ALPHA); break;
case 2: glColor4d(1.0, 0.0, 0.0, ALPHA); break;
case 3: glColor4d(0.3, 0.2, 1.0, ALPHA); break;
case 4: glColor4d(0.5, 0.7, 1.0, ALPHA); break;
}
}
glBegin(GL_TRIANGLES);
glTexCoord2d(0.0, 0.0); glVertex2d(n->x[0], n->y[0]);
glTexCoord2d(1.0, 0.0); glVertex2d(n->x[1], n->y[1]);
glTexCoord2d(0.5, SQRT3_2); glVertex2d(n->x[2], n->y[2]);
glEnd();
}
}
static void MeshDraw(node_t *n)
{
if(!n->child[0] || !n->child[1])
{
NodeDraw(n, 0, 0);
}
else
{
// recurse
MeshDraw(n->child[0]);
MeshDraw(n->child[1]);
}
}
static void InitializeMesh(int type)
{
MP = MESH;
if(type == 0) // two [4.8.8] triangles
{
node_t *n1 = MP++; memset(n1, 0, sizeof(node_t));
node_t *n2 = MP++; memset(n2, 0, sizeof(node_t));
n1->type = n2->type = 0;
n1->x[0] = n1->y[1] = 1.0;
n2->x[1] = n2->y[0] = 1.0;
n2->x[2] = n2->y[2] = 1.0;
n1->adjacent[0] = n2;
n2->adjacent[0] = n1;
MESH_TREES = 2;
}
else if(type == 1) // four [4.6.12] triangles
{
node_t *n1 = MP++; memset(n1, 0, sizeof(node_t));
node_t *n2 = MP++; memset(n2, 0, sizeof(node_t));
node_t *n3 = MP++; memset(n3, 0, sizeof(node_t));
node_t *n4 = MP++; memset(n4, 0, sizeof(node_t));
n1->type = n2->type = n3->type = n4->type = 1;
n1->x[0] = n2->x[1] = n2->x[2] = SQRT3_2;
n3->x[0] = n4->x[1] = n4->x[2] = SQRT3_2;
n1->y[1] = n2->y[0] = n2->y[2] = 0.5;
n3->y[0] = n3->y[2] = n4->y[1] = 0.5;
n3->y[1] = n4->y[0] = n4->y[2] = 1.0;
n1->adjacent[0] = n2;
n2->adjacent[0] = n1;
n2->adjacent[2] = n3;
n3->adjacent[2] = n2;
n3->adjacent[0] = n4;
n4->adjacent[0] = n3;
MESH_TREES = 4;
}
else if(type == 2) // four [4.6.12] triangles, two [4.8.8] triangles
{
double a = 0.366025403784438646763723170752936;
node_t *n1 = MP++; memset(n1, 0, sizeof(node_t));
node_t *n2 = MP++; memset(n2, 0, sizeof(node_t));
node_t *n3 = MP++; memset(n3, 0, sizeof(node_t));
node_t *n4 = MP++; memset(n4, 0, sizeof(node_t));
node_t *n5 = MP++; memset(n5, 0, sizeof(node_t));
node_t *n6 = MP++; memset(n6, 0, sizeof(node_t));
n1->type = n2->type = n3->type = n4->type = 1;
n5->type = n6->type = 0;
n3->x[0] = n4->x[1] = n5->x[1] = n6->x[0] = a;
n1->y[1] = n4->y[0] = n5->y[2] = a;
n1->x[0] = n5->x[0] = n6->x[1] = n2->x[1] = 1.0 - a;
n2->y[0] = n3->y[1] = n6->y[2] = 1.0 - a;
n2->x[0] = n2->x[2] = n3->x[1] = 1.0;
n3->x[2] = n6->x[2] = 1.0;
n3->y[0] = n3->y[2] = n4->y[1] = 1.0;
n4->y[2] = n5->y[1] = n6->y[0] = 1.0;
n1->adjacent[0] = n5;
n2->adjacent[0] = n6;
n3->adjacent[0] = n6;
n4->adjacent[0] = n5;
n5->adjacent[0] = n6;
n5->adjacent[1] = n4, n5->adjacent[2] = n1;
n6->adjacent[0] = n5;
n6->adjacent[1] = n2, n6->adjacent[2] = n3;
MESH_TREES = 6;
}
else if(type == 3) // four [4.8.8] triangles, two [4.6.12] triangles
{
double a = 0.366025403784438646763723170752936;
node_t *n1 = MP++; memset(n1, 0, sizeof(node_t));
node_t *n2 = MP++; memset(n2, 0, sizeof(node_t));
node_t *n3 = MP++; memset(n3, 0, sizeof(node_t));
node_t *n4 = MP++; memset(n4, 0, sizeof(node_t));
node_t *n5 = MP++; memset(n5, 0, sizeof(node_t));
node_t *n6 = MP++; memset(n6, 0, sizeof(node_t));
n1->type = n2->type = n3->type = n4->type = 0;
n5->type = n6->type = 1;
n3->x[0] = n4->x[1] = n5->x[1] = n6->x[0] = a;
n1->y[1] = n4->y[0] = n5->y[2] = 1.0 - a;
n1->x[0] = n5->x[0] = n6->x[1] = n2->x[1] = 1.0 - a;
n2->y[0] = n3->y[1] = n6->y[2] = a;
n2->x[0] = n2->x[2] = n3->x[1] = 1.0;
n3->x[2] = n6->x[2] = 1.0;
n3->y[0] = n3->y[2] = n4->y[1] = 1.0;
n4->y[2] = n5->y[1] = n6->y[0] = 1.0;
n1->adjacent[0] = n5;
n2->adjacent[0] = n6;
n3->adjacent[0] = n6;
n4->adjacent[0] = n5;
n5->adjacent[0] = n6;
n5->adjacent[1] = n4, n5->adjacent[2] = n1;
n6->adjacent[0] = n5;
n6->adjacent[1] = n2, n6->adjacent[2] = n3;
MESH_TREES = 6;
}
else if(type == 4) // two freeform triangles
{
node_t *n1 = MP++; memset(n1, 0, sizeof(node_t));
node_t *n2 = MP++; memset(n2, 0, sizeof(node_t));
n1->type = n2->type = 4;
n1->x[0] = n1->y[1] = 1.0;
n2->x[1] = n2->y[0] = 1.0;
n2->x[2] = n2->y[2] = 1.0;
n1->adjacent[0] = n2;
n2->adjacent[0] = n1;
MESH_TREES = 2;
}
else MESH_TREES = 0;
VALUE = 0;
HILITE = NULL;
}
static void InitializeColorTable(void)
{
for(int depth = 0; depth <= MAX_DEPTH; depth++)
{
double *c = COLOR_TABLE + depth*3;
double v = (double)depth / (double)MAX_DEPTH;
c[0] = fabs(v - 0.5)*2.0;
c[1] = 1.0 - pow(v, 1.3);
c[2] = pow(v, 0.7);
}
}
static void InitializeTexture(int size, int style)
{
long *tex = (long*)malloc(sizeof(long)*size*size);
if(!tex) exit(-1); // out of memory
for(int y = 0; y < size; y++)
{
for(int x = 0; x < size; x++)
{
double u = (double)x / (double)(size-1);
double v = (double)y / (double)(size-1);
double t1 = v;
double t2 = (0.0+u)*SQRT3_2 - v*0.5;
double t3 = (1.0-u)*SQRT3_2 - v*0.5;
double t = t1*t2*t3*16.0;
if(t < 0.0) t = 0.0;
if(t > 1.0) t = 1.0;
switch(style)
{
case 0: t = 0.0 + pow(t, 0.25); break;
case 1: t = 1.0 - pow(t, 0.41); break;
}
if(t < 0.25) t = 0.25;
tex[x + y*size] = 0x01010101 * (long)(t*255.0);
}
}
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGB8,
size, size, GL_RGBA,
GL_UNSIGNED_BYTE, tex);
free(tex);
}
static void GLUT__Display(void)
{
if(ALPHA == 1.0)
{
glClear(GL_COLOR_BUFFER_BIT);
}
for(int i = 0; i < MESH_TREES; i++)
{
MeshDraw(MESH + i);
}
if(HILITE)
{
glColor4d(1.0, 0.0, 0.0, 1.0);
NodeDraw(HILITE, 1, HLT__K);
}
glFlush();
glutSwapBuffers();
}
static void GLUT__Reshape(int width, int height)
{
if(width > height) width = height;
if(height < width ) height = width;
VIEW_X = width;
VIEW_Y = height;
glViewport(0, 0, width, height);
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
glOrtho(0.0, 1.0, 0.0, 1.0, -1.0, 1.0);
}
static void GLUT__Keyboard(unsigned char key, int x, int y)
{
if(key == 27 ) exit(0); // ESC key quits program
if(key == '1') InitializeMesh(0);
if(key == '2') InitializeMesh(1);
if(key == '3') InitializeMesh(2);
if(key == '4') InitializeMesh(3);
if(key == '5') InitializeMesh(4);
if(key == '6') COLOR_MODE = 0;
if(key == '7') COLOR_MODE = 1;
if(key == '8') COLOR_MODE = 2;
if(key == '9') COLOR_MODE = 3;
if(key == 'a') InitializeTexture(256, 0);
if(key == 'b') InitializeTexture(256, 1);
}
static void GLUT__Special(int key, int x, int y)
{
}
static void GLUT__Mouse(int button, int state, int x, int y)
{
y = VIEW_Y - y;
double u = (double)x / (double)VIEW_X;
double v = (double)y / (double)VIEW_Y;
if(state == GLUT_DOWN)
{
int k;
node_t *n = NodePick(u, v, &k);
if(n && n->depth < MAX_DEPTH)
{
static int ROT1[] = {1,2,0,1,2,0};
static int ROT2[] = {2,0,1,2,0,1};
if(k == 0) NodeCycle(n, ROT2); else
if(k == 1) NodeCycle(n, ROT1);
NodeSplit(n, NULL, VALUE++);
}
HILITE = n;
}
}
static void GLUT__Motion(int x, int y)
{
y = VIEW_Y - y;
double u = (double)x / (double)VIEW_X;
double v = (double)y / (double)VIEW_Y;
if(CONTINUOUS_SPLIT)
{
int k;
node_t *n = NodePick(u, v, &k);
if(n && n->depth < MAX_DEPTH)
{
static int ROT1[] = {1,2,0,1,2,0};
static int ROT2[] = {2,0,1,2,0,1};
if(k == 0) NodeCycle(n, ROT2); else
if(k == 1) NodeCycle(n, ROT1);
NodeSplit(n, NULL, VALUE++);
}
HILITE = n;
}
}
static void GLUT__PassiveMotion(int x, int y)
{
y = VIEW_Y - y;
double u = (double)x / (double)VIEW_X;
double v = (double)y / (double)VIEW_Y;
HILITE = NodePick(u, v, &HLT__K);
}
static void GLUT__Idle(void)
{
glutPostRedisplay();
}
int main(int argc, char *argv[])
{
fprintf(stdout, "MeshToy: RTIN Subdivision Demo\n");
fprintf(stdout, "(c) Bernie Freidin, 2000\n\n");
fprintf(stdout, "Keys:\n");
fprintf(stdout, " Left-click to subdivide triangles\n");
fprintf(stdout, " 1-5 to reset mesh (1 is standard RTIN)\n");
fprintf(stdout, " 6-9 to set color mode\n");
fprintf(stdout, " A/B to set invert mode\n");
fprintf(stdout, " ESCAPE to quit\n\n");
ASSERT(1); // force compiler to include ASSERT function
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowSize(VIEW_X, VIEW_Y);
int win_id = glutCreateWindow(argv[0]);
glutDisplayFunc(GLUT__Display);
glutReshapeFunc(GLUT__Reshape);
glutKeyboardFunc(GLUT__Keyboard);
glutSpecialFunc(GLUT__Special);
glutMouseFunc(GLUT__Mouse);
glutMotionFunc(GLUT__Motion);
glutPassiveMotionFunc(GLUT__PassiveMotion);
glutIdleFunc(GLUT__Idle);
glutSetCursor(GLUT_CURSOR_CROSSHAIR);
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glClear(GL_COLOR_BUFFER_BIT);
glLineWidth(2.0f);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
glEnable(GL_TEXTURE_2D);
if(ALPHA)
{
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
glEnable(GL_BLEND);
}
MESH = (node_t*)malloc(sizeof(node_t)*MAX_NODES);
if(!MESH) exit(-1); // out of memory
InitializeMesh(0);
InitializeColorTable();
InitializeTexture(256, 0);
glutMainLoop();
return 0;
}
This page © Bernie Freidin, 2000.