HypEngine Demo Source

by Bernie Freidin © 1999-2000



hypertile.cpp vector.cpp
hypertile.h vector.h


// **************************************************************************************
// **************************************************************************************
// *** HyperTile.cpp (HypEngine)
// *** Copyright (c) 1999-2000 Bernie Freidin
// *** Ported to OpenGL on 2-6-00 
// *** (3Dfx Glide code is still here, but commented-out)
// **************************************************************************************
// **************************************************************************************

#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <time.h>
#include <gl/glut.h>
#include "vector.h"
#include "hypertile.h"

static int WAIT_FOR_CLOCK = 1;
static int MAIN__STOP = 0;
static int TILES_HIT = 0;

#define FLOOR_TEX_ID 1 // 1
#define WALL_TEX_ID 2 // 5
#define CORNER_TEX_ID 2 // 5
#define CEIL_TEX_ID 3

int MAIN__texcount = 4;
unsigned int MAIN__texid[10];
char MAIN__texnames[][200] = {
	"Textures/floor.tga", // floor
	"Textures/wall.tga", // wall,corner
	"Textures/wall.tga", // ceiling
	""
};

static void MAIN__LoadTGA(char *fpath)
{
	FILE *tga = fopen(fpath, "rb");
	if(!tga)
	{
		fprintf(stderr, "\n\ncan't find required TGA (%s)\n", fpath);
		fprintf(stderr, "PRESS RETURN TO EXIT\n");
		char wait[200];
		gets(wait);
		exit(-1);
	}
	// ****************************
	//   2  bytes offset = N
	//   10 bytes of crap
	//   2  bytes = WIDTH
	//   2  bytes = HEIGHT
	//   2  bytes = BPP (24)
	//   N  bytes of crap..
	//   PIXEL DATA FOLLOWS (24bpp)
	// ****************************
	
	short offset, width, height;
	fread(&offset, sizeof(short), 1, tga);
	fseek(tga, 10, SEEK_CUR);
	fread(&width,  sizeof(short), 1, tga);
	fread(&height, sizeof(short), 1, tga);
	fseek(tga, offset+2, SEEK_CUR);
	
	char *rgb = (char*)malloc(3*width*height);
	fread(rgb, 3, width*height, tga);
	fclose(tga);
	
	// Swap red and blue values
	
	for(int i = 0; i < width*height; i++)
	{
		char tmp = rgb[i*3];
		rgb[i*3] = rgb[i*3+2];
		rgb[i*3+2] = tmp;
	}
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, width, height, GL_RGB, GL_UNSIGNED_BYTE, rgb);
    
	free(rgb);
}

static void MAIN__LoadAlphaMask(int size)
{
	char *rgb = (char*)malloc(3*size*size);
	
	for(int y = 0; y < size; y++)
	{
		for(int x = 0; x < size; x++)
		{
			vec2 v = vec2((float)(x-size/2),
			              (float)(y-size/2));
			
			v /= (float)(size/2);
			
			float fog  = 1.f - (float)pow(v*v, 3.f);
			if(fog < 0.f) fog = 0.f;
			if(fog > 1.f) fog = 1.f;
			int fogi = (int)(fog * 255.f);
			
			rgb[x*3 + y*size*3 + 0] = fogi;
			rgb[x*3 + y*size*3 + 1] = fogi;
			rgb[x*3 + y*size*3 + 2] = fogi;
		}
	}
	gluBuild2DMipmaps(GL_TEXTURE_2D, 3, size, size, GL_RGB, GL_UNSIGNED_BYTE, rgb);
    free(rgb);
}

static void MAIN__TrackFPS(void)
{
	static double sum = 0.f;
	static double num = 0.f;
	static int clk_count = 1;
	static clock_t clk1;
	static clock_t clk2;
	
	clk1 = clk2;
	clk2 = clock();
	sum += (double)(clk2-clk1);
	num += 1.f;
	
	if(MAIN__STOP)
	{
	//	FILE *fps_out = fopen("fps.txt", "w");
	//	if(!fps_out) return;
	//	double fps = (double)CLOCKS_PER_SEC * num / sum;
	//	fprintf(fps_out, "AVERAGE FPS= %lf\n", fps);
	//	fclose(fps_out);
		exit(0);
	}
}

static void MAIN__RenderScene(void)
{
}

// *********************************************************
// Backface rejection improves vertex stability by balancing
// the recursion tree. However, it also occasionally omits a
// tile when rendering near the border. This should be fixed
// at some point..
// 
// (note - this is NOT triangle backface rejection!)
// *********************************************************

#define BACKFACE_REJECT 1

// **************************************************************************************
// **************************************************************************************
// *** Structures
// **************************************************************************************
// **************************************************************************************

struct vertex_t
{
	vec3 v; // viewspace coords (x,y,z=1)
	vec3 c; // color value (r,g,b)
	float a;
	vec2 t; // texture coords (s,t)
};

#define OBJ_FLAG_PLAYER 0x001
#define OBJ_FLAG_GHOST  0x002
#define OBJ_FLAG_MOVING 0x004
#define OBJ_FLAG_CAMERA 0x008

struct tile_t;
struct obj_t
{
	int    flags;
	float  r;     // object radius
	float  r_max; // object radius (target)
	mat3   m;     // object transform
	mat3   m_inv; // inverse transform
	tile_t *tile; // parent tile
	obj_t  *next; // next object in tile
	int    verts;
	vec3   color1;
	vec3   color2;
};

struct mesh_t // (432 bytes)
{
	tile_t *tile;
	vec2   v[49]; // mesh verts (poincare)
	mat3   m;
	vec3   color;
};

struct tile_t // (36 bytes)
{
	int    flags; // 4 bits WALL, 4 bits CORNER
	int    frame_rendered;
	int    frame_visited;
	tile_t *next[4];
	obj_t  *obj;
	mesh_t *mesh;
};

// **************************************************************************************
// **************************************************************************************
// *** Defines
// **************************************************************************************
// **************************************************************************************

//Link: http://teach.belmont.edu/ht/applet/htbu.html

#define MAX_WORLD_OBJECTS     4000
#define MAX_ONSCREEN_OBJECTS   500
#define MAX_WORLD_TILES      30000
#define MAX_ONSCREEN_TILES    1000

#define RENDER 1
#if RENDER
/* - 3Dfx Glide Macros
#define RenderPoly(v,c) \
	grDrawVertexArrayContiguous(GR_POLYGON, c, v, sizeof(vertex_t))
#define RenderTriFan(v,c) \
	grDrawVertexArrayContiguous(GR_TRIANGLE_FAN, c, v, sizeof(vertex_t))
#define RenderTriStrip(v,c) \
	grDrawVertexArrayContiguous(GR_TRIANGLE_STRIP, c, v, sizeof(vertex_t))
#define RenderWireframe(v,c) \
	grDrawVertexArrayContiguous(GR_LINE_STRIP, c, v, sizeof(vertex_t)); \
	grDrawVertexArrayContiguous(GR_LINE_STRIP, c/2, v, sizeof(vertex_t)*2)
#define RenderPoint(v) grDrawPoint(v)
*/
// OpenGL Macros

static void RenderPoly(vertex_t *v, int c)
{
	glBegin(GL_POLYGON);
	
	for(int i = 0; i < c; i++)
	{
		glColor3fv((GLfloat*)&v[i].c);
		glTexCoord2fv((GLfloat*)&v[i].t);
		glVertex3fv((GLfloat*)&v[i].v);
	}
	glEnd();
}

static void RenderTriFan(vertex_t *v, int c)
{
	glBegin(GL_TRIANGLE_FAN);
	
	for(int i = 0; i < c; i++)
	{
		glColor3fv((GLfloat*)&v[i].c);
		glTexCoord2fv((GLfloat*)&v[i].t);
		glVertex3fv((GLfloat*)&v[i].v);
	}
	glEnd();
}

static void RenderTriStrip(vertex_t *v, int c)
{
	glBegin(GL_TRIANGLE_STRIP);
	
	for(int i = 0; i < c; i++)
	{
		glColor3fv((GLfloat*)&v[i].c);
		glTexCoord2fv((GLfloat*)&v[i].t);
		glVertex3fv((GLfloat*)&v[i].v);
	}
	glEnd();
}

#define RenderWireframe(v,c) // not used
#define RenderPoint(v) // not used

#else
#define RenderPoly(v,c)
#define RenderTriFan(v,c)
#define RenderTriStrip(v,c)
#define RenderWireframe(v,c)
#define RenderPoint(v)
#endif

/* - 3Dfx Glide
int Render_LoadTexture(char *fn)
{
	GrTexInfo info;
	Gu3dfInfo info2;
	
	long startaddr = grTexMinAddress(GR_TMU0);
	
	// download from disk
	
	gu3dfGetInfo(fn, &info2); info2.data = malloc(info2.mem_required);
	gu3dfLoad   (fn, &info2);
	
	// setup
	
	info.smallLodLog2    = info2.header.small_lod;
	info.largeLodLog2    = info2.header.large_lod;
	info.aspectRatioLog2 = info2.header.aspect_ratio;
	info.format          = info2.header.format;
	info.data            = info2.data;
	
	// upload to hardware
	
	grTexDownloadMipMap(GR_TMU0, startaddr, GR_MIPMAPLEVELMASK_BOTH, &info);
	grTexSource        (GR_TMU0, startaddr, GR_MIPMAPLEVELMASK_BOTH, &info);
	
	free(info2.data);
	
	return 0; // ok
}
*/

// **************************************************************************************
// **************************************************************************************
// *** Global Variables and Macros
// **************************************************************************************
// **************************************************************************************

#define TILE_FLOOR_FLAG 0x0100
#define TILE_FREE_FLAG  0x0200

#define SetWall(tile,k)     (tile)->flags |=  ( 1<<(k))
#define SetCorner(tile,k)   (tile)->flags |=  (16<<(k))
#define SetFloor(tile)      (tile)->flags |= TILE_FLOOR_FLAG
#define SetFree(tile)       (tile)->flags |= TILE_FREE_FLAG
#define ClearWall(tile,k)   (tile)->flags &= ~( 1<<(k))
#define ClearCorner(tile,k) (tile)->flags &= ~(16<<(k))
#define ClearFloor(tile)    (tile)->flags &= ~TILE_FLOOR_FLAG
#define ClearFree(tile)     (tile)->flags &= ~TILE_FREE_FLAG
#define IsWall(tile,k)     ((tile)->flags &   ( 1<<(k)))
#define IsCorner(tile,k)   ((tile)->flags &   (16<<(k)))
#define IsFloor(tile)      ((tile)->flags &   TILE_FLOOR_FLAG)
#define IsFree(tile)       ((tile)->flags &   TILE_FREE_FLAG)

static obj_t  ObjectList[MAX_WORLD_OBJECTS];
static tile_t TileList[MAX_WORLD_TILES];
static mesh_t MeshList[MAX_ONSCREEN_TILES];
static int    ObjectCount = 0;
static int    TileCount   = 0;
static int    MeshCount   = 0;

static float TileLength   = 0.485898000000000f; // Euclidean
static float TileAngle    = 1.256637061435917f; // 2Pi/5
static float ProjectionScale;
static float ProjectionMorph;
static int   CurrentFrame = 1;
static float LimR;

static mat3 MESH__M[16]; // Tile-to-tile traversal transformations
static vec2 MESH__T[49]; // Texture coordinates
static vec2 MESH__V[49]; // Untransformed tile vertices

// **************************************************************************************
// **************************************************************************************
// *** Private Function Prototypes
// **************************************************************************************
// **************************************************************************************

static tile_t *AddTile(tile_t *next);
static int     FindIndex(tile_t *to, tile_t *from);
static void    CurlLeft(tile_t *tile);
static void    CurlRight(tile_t *tile);

static int IntersectCS(float r, vec2 &a, vec2 &b);

static void DrawFloor(tile_t *tile, float height);
static void DrawWall(tile_t *tile, int k, float height);
static void DrawCorner(tile_t *tile, int k, float height);
static void DrawObjects(tile_t *tile);
static void DrawScene(obj_t *obj);

static void VisitTile(tile_t *tile);
static void PlaceObj(obj_t *obj, tile_t *tile);
static void UpdateObj(obj_t *obj, mat3 &m);
static int  CollideObjNode(obj_t *obj);

static int  GetSwapSync(void);

// **************************************************************************************
// **************************************************************************************
// *** Functions For Generating Tiling Topology
// **************************************************************************************
// **************************************************************************************

static tile_t *AddTile(tile_t *next)
{
	tile_t *tile = &TileList[TileCount++];
	
	tile->frame_rendered = 0;
	tile->frame_visited  = 0;
	tile->next[0]        = next;
	tile->next[1]        = NULL;
	tile->next[2]        = NULL;
	tile->next[3]        = NULL;
	tile->obj            = NULL;
	
	SetWall(tile, 0);
	SetWall(tile, 1);
	SetWall(tile, 2);
	SetWall(tile, 3);
	SetFloor(tile);
	SetFree(tile);
	
	return tile;
}

static int FindIndex(tile_t *to, tile_t *from)
{
	if(from->next[0] == to) return 0;
	if(from->next[1] == to) return 1;
	if(from->next[2] == to) return 2;
	if(from->next[3] == to) return 3;
	return -1;
}

static void CurlLeft(tile_t *tile)
{
	tile_t *save = tile;
	int i = 0, count = 1;
	
	while(tile->next[i])
	{
		tile_t *next = tile->next[i];
		i = (FindIndex(tile, next) + 1)&3;
		tile = next;
		count++;
	}
	if(count >= 5)
	{
		tile->next[i] = save;
		save->next[3] = tile;
	}
}

static void CurlRight(tile_t *tile)
{
	tile_t *save = tile;
	int i = 0, count = 1;
	
	while(tile->next[i])
	{
		tile_t *next = tile->next[i];
		i = (FindIndex(tile, next) - 1)&3;
		tile = next;
		count++;
	}
	if(count >= 5)
	{
		tile->next[i] = save;
		save->next[1] = tile;
	}
}

static int IntersectCS(float r, vec2 &a, vec2 &b)
{
	// **********************************************
	// Returns 1 if circle and line segment intersect
	// **********************************************
	
	float tmp1, tmp2;
	vec2 n;
	
	r*=r;
	
	if(a*a < r || b*b < r) return 1;
	n = vec2(a.y - b.y, b.x - a.x);
	tmp1 = a*n;
	if(tmp1*tmp1 >= (n*n)*r) return 0;
	tmp1 = a.x*n.y - a.y*n.x;
	tmp2 = b.x*n.y - b.y*n.x;
	return (tmp1 > 0.f) != (tmp2 > 0.f);
}

static vec2 HProject(vec2 &v)
{
	float  q = ProjectionMorph * (float)sqrt(1.f - v*v);
	return v * ProjectionScale / (1.f + q);
}

static void UpdateMesh(mesh_t *mesh)
{
#define UpdateMeshVert(i, table) \
	v[table[i]] = HProject((mesh->m*MESH__V[table[i]]))
#define CopyMeshVert(i, j, table) \
	v[table[i]] = next->mesh->v[table[j]]
	
	tile_t *tile = mesh->tile;
	vec2   *v    = mesh->v;
	
	static char outer_edge_table[]   = {1,2,3,4,5,13,20,27,34,41,47,
	                                    46,45,44,43,35,28,21,14,7};
	static char inner_edge_table[]   = {9,10,11,19,26,33,39,38,37,
	                                    29,22,15};
	static char outer_corner_table[] = {0,6,48,42};
	static char inner_corner_table[] = {8,12,40,36};
	static char inner_vert_table[]   = {16,17,18,23,24,25,30,31,32};
	
	int i, k, k1, k2;
	
	for(i = 0; i < 9; i++)
	{
		UpdateMeshVert(i, inner_vert_table);
	}
	for(k = 0; k < 4; k++)
	{
		tile_t *next = tile->next[k];
		
		// ****************************
		// Update/copy OUTER EDGE verts
		// ****************************
		
		if(next && next->frame_rendered == CurrentFrame)
		{
			k1 = 5*k;
			k2 = 5*FindIndex(tile, next) + 4;
			
			for(i = 0; i < 5; i++)
			{
				CopyMeshVert(k1 + i, k2 - i, outer_edge_table);
			}
		}
		else
		{
			k1 = 5*k;
			
			for(i = 1; i < 4; i++)
			{
				UpdateMeshVert(k1 + i, outer_edge_table);
			}
			if(IsCorner(tile, k) || (IsWall(tile, (k-1)&3) &&
			                        !IsWall(tile, k)))
			{
				UpdateMeshVert(k1, outer_edge_table);
			}
			if(IsCorner(tile, (k+1)&3) || (IsWall(tile, (k+1)&3) &&
			                              !IsWall(tile, k)))
			{
				UpdateMeshVert(k1 + 4, outer_edge_table);
			}	  
		}
		// ******************************
		// Update/copy OUTER CORNER verts
		// ******************************
		
		if(next && next->frame_rendered == CurrentFrame)
		{
			k2 = (FindIndex(tile, next) + 1)&3;
			
			CopyMeshVert(k, k2, outer_corner_table);
		}
		else
		{
			next = tile->next[(k-1)&3];
			
			if(next && next->frame_rendered == CurrentFrame)
			{
				k2 = FindIndex(tile, next);
				
				CopyMeshVert(k, k2, outer_corner_table);
			}
			else
			{
				UpdateMeshVert(k, outer_corner_table);
			}
		}
		// ***********************
		// Update INNER EDGE verts
		// ***********************
		
		k1 = 3*k;
		
		if(IsWall(tile, k))
		{
			for(i = 0; i < 3; i++)
			{
				UpdateMeshVert(k1 + i, inner_edge_table);
			}
		}
		// *************************
		// Update INNER CORNER verts
		// *************************
		
		if(IsWall(tile, k) && IsWall(tile, (k-1)&3))
		{
			UpdateMeshVert(k, inner_corner_table);
		}
	}
#undef UpdateMeshVert
#undef CopyMeshVert
}

static void DrawFloor(tile_t *tile, float height)
{
	mesh_t *mesh = tile->mesh;
	
	int i, j;
	
	if(height == 1.f)
		glBindTexture(GL_TEXTURE_2D, MAIN__texid[FLOOR_TEX_ID]);
	else
		glBindTexture(GL_TEXTURE_2D, MAIN__texid[CEIL_TEX_ID]);
	
	// ************
	// Compute fade
	// ************
	
//	mesh->color = vec3(.5f, .5f, .5f);
	mesh->color = vec3(.0f, .0f, .0f);
	if(height == 1.f)
	{
		float fade;
		
		if(tile->frame_visited < 1)
		{
			fade = 0.f;
		}
		else
		{
			int frames = CurrentFrame - tile->frame_visited;
			if(frames > 120) fade = .166f;
			else fade = .166f + .633f*(float)(120-frames)/120.f;
		}
		mesh->color = vec3(fade, fade, fade);
	}
	// ***************************
	// Adjust tri-strip parameters
	// ***************************
	
	int index_i[5] = {0, 2, 3, 4, 6};
	int index_j[5] = {0, 14, 21, 28, 42};
	int index_n[4] = {0, 1, 2, 3};
	int count_n[4] = {10, 10, 10, 10};
	
	if(IsWall  (tile, 0)) index_j[0] += 7;
	if(IsWall  (tile, 2)) index_j[4] -= 7;
	if(IsWall  (tile, 3)) index_i[0] += 1;
	if(IsWall  (tile, 1)) index_i[4] -= 1;
	if(IsCorner(tile, 0)) count_n[0] -= 2, index_n[0] += 2;
	if(IsCorner(tile, 1)) count_n[0] -= 2;
	if(IsCorner(tile, 3)) count_n[3] -= 2, index_n[3] += 2;
	if(IsCorner(tile, 2)) count_n[3] -= 2;
	
	// *****************
	// Render tri-strips
	// *****************
	
	vertex_t v[13];
	
	vec2 *v_src = mesh->v + index_j[0];
	vec2 *t_src = MESH__T + index_j[0];
	
	for(i = 0; i < 13; i++)
	{
		v[i].c = mesh->color;
	}
	for(i = 0; i < 5; i++)
	{
		int idx = index_i[i];
		v[2*i].v = v_src[idx];
		v[2*i].t = t_src[idx];
		v[2*i].v.z = height;
	}
	for(j = 1; j < 5; j++)
	{
		v_src = mesh->v + index_j[j];
		t_src = MESH__T + index_j[j];
		
		for(i = 0; i < 5; i++)
		{
			int idx = index_i[i];
			v[2*i+j].v = v_src[idx];
			v[2*i+j].t = t_src[idx];
			v[2*i+j].v.z = height;
		}
		RenderTriStrip(v + index_n[j-1], count_n[j-1]);
	}
	// **********************
	// Render corner polygons
	// **********************
	
	static int corner_index_table[] = {
		16, 2, 14, 1, 7,
		18, 20, 4, 13, 5,
		32, 46, 34, 47, 41,
		30, 28, 44, 35, 43};
	
	for(j = 0; j < 4; j++)
	{
		if(!IsCorner(tile, j)) continue;
		
		int *corner_index = corner_index_table + 5*j;
		
		for(i = 0; i < 5; i++)
		{
			int idx = corner_index[i];
			v[i].v = mesh->v[idx];
			v[i].t = MESH__T[idx];
			v[i].v.z = height;
		}
		RenderTriStrip(v, 5);
	}
}

static void DrawWall(tile_t *tile, int k, float height)
{
	mesh_t *mesh = tile->mesh;
	
	int i;
	glBindTexture(GL_TEXTURE_2D, MAIN__texid[WALL_TEX_ID]);
	
//	if(!IntersectCS(LimR, tc->vin[k], tc->vin[k+4]) &&
//	   !IntersectCS(LimR, tc->v[k], tc->v[(k+1)&3]) )
//	{
//		return;
//	}
	static char outer_index_table[] = {
		0,2,3,4,6,20,27,34,48,46,45,44,42,28,21,14,0};
	static char inner_index_table[] = {
		7,9,10,11,13,5,19,26,33,47,41,39,38,37,35,43,29,22,15,1};
	static char index_adjust_table[] = {
		8,12,40,36,8};
	static float texcoord_table[] = {
		0.f, .2500f, .5000f, .7500f, 1.f,
		0.f, .2857f, .5714f, .8571f, 1.f,
		0.f, .1429f, .4286f, .7143f, 1.f,
		0.f, .1666f, .5000f, .8333f, 1.f};
	
	vertex_t v[10];
	
	char  *outer_index = outer_index_table + k*4;
	char   inner_index[5];
	float *texcoord = texcoord_table;
	
	memcpy(inner_index, inner_index_table + k*5, 5);
	
	if(IsWall(tile, (k-1)&3))
	{
		inner_index[0] = index_adjust_table[k];
		texcoord += 10;
	}
	if(IsWall(tile, (k+1)&3))
	{
		inner_index[4] = index_adjust_table[k+1];
		texcoord += 5;
	}
	for(i = 0; i < 5; i++)
	{
		vertex_t *vrt = v + 2*i;
		vrt->v = mesh->v[outer_index[i]];
		vrt->t = vec2(0.f, texcoord_table[i]*2.f);
		vrt->c = vec3(.5f, .5f, .5f);
		vrt->v.z = height;
	}
	for(i = 0; i < 5; i++)
	{
		vertex_t *vrt = v + 2*i + 1;
		vrt->v = mesh->v[inner_index[i]];
		vrt->t = vec2(.66f, texcoord[i]*2.f);
		vrt->c = mesh->color;
	}
	RenderTriStrip(v, 10);
}

static void DrawCorner(tile_t *tile, int k, float height)
{
	mesh_t *mesh = tile->mesh;
	
	glBindTexture(GL_TEXTURE_2D, MAIN__texid[CORNER_TEX_ID]);
	
	vertex_t v[3];
	
	static int corner_index_table[12] =
		{0,7,1,6,5,13,48,41,47,42,43,35};
	
	int *corner_index = corner_index_table + 3*k;
	
	v[0].v = mesh->v[corner_index[0]];
	v[1].v = mesh->v[corner_index[1]];
	v[2].v = mesh->v[corner_index[2]];
	v[0].t = vec2(0.f, 0.f);
	v[1].t = vec2(.66f, 0.f);
	v[2].t = vec2(.25f, .66f);
	v[0].c = vec3(.5f, .5f, .5f);
	v[1].c = mesh->color;
	v[2].c = mesh->color;
	v[0].v.z = height;
	
	RenderPoly(v, 3);
}

static void DrawObjects(tile_t *tile)
{
	obj_t *obj = tile->obj;
	
	int i;
	
//	glBindTexture(GL_TEXTURE_2D, MAIN__texid[2]);
	glDisable(GL_TEXTURE_2D);
	while(obj)
	{
		vertex_t v[18]; // MAX(obj->verts) = 16
		mat3 m;
		float theta, dt;
		
		if(obj->flags & OBJ_FLAG_GHOST)
		{
			obj = obj->next;
			continue;
		}
		// *************
		// Expand radius
		// *************
		
		if(obj->r < obj->r_max)
		{
			obj->r += .005f;
			
			if(obj->r > obj->r_max)
			{
				obj->r = obj->r_max;
			}
		}
		// ***********
		// Draw object
		// ***********
		
		m     = tile->mesh->m * obj->m_inv;
		theta = 0.f;
		dt    = 6.2831853f / (float)obj->verts;
		
		v[0].v = HProject(vec2(m.x.z, m.y.z) / m.z.z);
		v[0].c =  vec3(1.f, 1.f, 1.f);
		v[0].v.z = .9f;
		
		for(i = 0; i < obj->verts; i++, theta += dt)
		{
			float s = (float)sin(theta);
			float c = (float)cos(theta);
			
			vec2 tmp = m * (vec2(c, s) * obj->r);
			
			s = (1.f + s) * .5f;
			c = (1.f - s);
			
			v[i+1].v = HProject(tmp);
			v[i+1].c = obj->color1*s + obj->color2*c;
		}
		v[i+1] = v[1];
		
		// 3Dfx Glide
		//grCullMode(GR_CULL_NEGATIVE);
		RenderTriFan(v, i+2);
		
		// ***********
		// Next object
		// ***********
		
		obj = obj->next;
	}
	glEnable(GL_TEXTURE_2D);
}

static void VisitTile(tile_t *tile)
{
	mesh_t *mesh = tile->mesh;
	
	int k;
	
	tile->frame_rendered = CurrentFrame;
	
	// ************************
	// Compute outside vertices
	// ************************
	
	// POTENTIAL BUG: Backface culling uses v_outer - since v_outer
	// is not shared between tiles, traversal may fail in rare cases
	
	vec2 v[4];
	
	v[0] = mesh->m*MESH__V[0];
	v[1] = mesh->m*MESH__V[6];
	v[2] = mesh->m*MESH__V[48];
	v[3] = mesh->m*MESH__V[42];
	
	for(k = 0; k < 4; k++)
	{
		// ***************************************
		// Reject if out-of-view, or already drawn
		// ***************************************
		
		tile_t *next = tile->next[k];
		
		if(!next || next->frame_rendered == CurrentFrame) continue;
		if(!IntersectCS(LimR, v[k], v[(k+1)&3])) continue;
		
		// ****************************
		// Reject if backward traversal
		// ****************************
		
#if BACKFACE_REJECT
		
		vec2 n(v[(k+1)&3].y - v[k].y,
		       v[k].x - v[(k+1)&3].x);
		
		float t = n*v[k];
		
		if(t > 0.f || (t == 0.f && tile > next)) continue;
#endif
		// ****************
		// Update next mesh
		// ****************
		
		mesh_t *mesh2 = next->mesh = &MeshList[MeshCount++];
		
		int i = (FindIndex(tile, next) - k + 2)&3;
		
		mesh2->m    = mesh->m*MESH__M[4*i + k];
		mesh2->tile = next;
		
		UpdateMesh(mesh2);
		
		// ***************************
		// Recurse to neighboring tile
		// ***************************
		
		VisitTile(next);
	}
}

static void PlaceObj(obj_t *obj, tile_t *tile)
{
	obj->next = tile->obj;
	obj->tile = tile;
	tile->obj = obj;
}

static void UpdateObj(obj_t *obj, mat3 &m)
{
	if(obj->flags & OBJ_FLAG_MOVING)
	{
		obj->m = mat3::horthogonalized(m);
		
		while(CollideObjNode(obj));
		
		obj->m_inv = obj->m.inverse();
	}
}

static int CollideObjNode(obj_t *obj)
{
	// ********************************
	// Returns 1 if changed - should be
	// called repeatedly until return 0
	// ********************************
	
	static char outer_index_table[] = {0, 6, 48, 42};
	static char inner_index_table[] = {7, 5, 41, 43, 13, 47, 35, 1};
	
	int k;
	
	if(~obj->flags & OBJ_FLAG_GHOST)
	{
		// ******************
		// Collide with walls
		// ******************
		
		for(k = 0; k < 4; k++)
		{
			if(!IsWall(obj->tile, k)) continue;
			
			vec2 a = obj->m*MESH__V[inner_index_table[k]];
			vec2 b = obj->m*MESH__V[inner_index_table[k+4]];
			vec2 n(b.y - a.y, a.x - b.x);
			
			float d = n*a;
			
			if(d*d < (n*n)*(obj->r*obj->r))
			{
				d /= n.len();
				vec2 a2 = a + n*(obj->r + d);
				vec2 b2 = b + n*(obj->r + d);
				obj->m = mat3::htransline(a2, b2, a, b)*obj->m;
				return 1;
			}
		}
		// ********************
		// Collide with corners
		// ********************
		
		for(k = 0; k < 4; k++)
		{
			if(!IsCorner(obj->tile, k)) continue;
			
			vec2 a = obj->m*MESH__V[inner_index_table[k]];
			vec2 b = obj->m*MESH__V[inner_index_table[((k-1)&3)+4]];
			vec2 n(b.y - a.y, a.x - b.x);
			
			float d = n*a;
			
			if(IntersectCS(obj->r, a, b))
			{
				d /= n.len();
				vec2 a2 = a + n*(obj->r + d);
				vec2 b2 = b + n*(obj->r + d);
				obj->m = mat3::htransline(a2, b2, a, b)*obj->m;
				return 1;
			}
			
		}
	}
	// **********************
	// Traverse between tiles
	// **********************
	
	for(k = 0; k < 4; k++)
	{
		if(obj->flags & OBJ_FLAG_GHOST)
		{
			if(IsWall(obj->tile, k)) continue;
		}
		vec2 a = obj->m*MESH__V[outer_index_table[k]];
		vec2 b = obj->m*MESH__V[outer_index_table[(k+1)&3]];
		
		if(a.x*b.y > a.y*b.x)
		{
			tile_t *next = obj->tile->next[k];
			
			if(!next) continue;
			
			// ******************
			// Update linked list
			// ******************
			
			if(obj->tile->obj == obj)
			{
				obj->tile->obj = obj->next;
			}
			else
			{
				obj_t *o2;
				
				for(o2 = obj->tile->obj; o2; o2 = o2->next)
				{
					if(o2->next == obj) break;
				}
				o2->next = obj->next;
			}
			obj->next = next->obj;
			next->obj = obj;
			
			// ****************
			// Update transform
			// ****************
			
			int i = (FindIndex(obj->tile, next) - k + 2)&3;
			
			obj->m = obj->m*MESH__M[4*i + k];
			obj->tile = next;
			
			return 1;
		}
	}
	return 0;
}

/* 3Dfx Glide
static int GetSwapSync(void)
{
	static long *swap_history = NULL;
	static long num_entires;
	
#if RENDER
	if(!swap_history)
	{
		grGet(GR_NUM_SWAP_HISTORY_BUFFER, 4, &num_entires);
		swap_history = (long*)malloc(sizeof(long) * num_entires);
		// test swap_history != NULL
	}
	grGet(GR_SWAP_HISTORY, 4*num_entires, swap_history);
	
	return swap_history[0] + 1;
#else
	return 1;
#endif
}
*/

static int cmpfn(const void *a, const void *b)
{
	mesh_t *mesh_a = *((mesh_t**)a);
	mesh_t *mesh_b = *((mesh_t**)b);
	
	float dist_a = mesh_a->v[24]*mesh_a->v[24];
	float dist_b = mesh_b->v[24]*mesh_b->v[24];
	
	if(dist_a < dist_b) return +1; else
	if(dist_a > dist_b) return -1; else
	return 0;
}

static void DrawScene(obj_t *obj)
{
	mesh_t *mesh;
	mesh_t *tmp_meshlist[MAX_ONSCREEN_TILES];
	
	int i, k;
	
	mesh       = obj->tile->mesh = &MeshList[0];
	mesh->m    = obj->m;
	mesh->tile = obj->tile;
	
	UpdateMesh(mesh);
	MeshCount = 1;
	
	VisitTile(obj->tile); // recursively visit tiles
	
	for(i = 0; i < MeshCount; i++)
	{
		tmp_meshlist[i] = &MeshList[i];
	}
	qsort(tmp_meshlist, MeshCount, sizeof(mesh_t*), cmpfn);
	
	// *******
	// Layer 1
	// *******
	
	for(i = 0; i < MeshCount; i++)
	{
		tile_t *tile = tmp_meshlist[i]->tile;
		
		if(IsFloor(tile))
		{
			DrawFloor(tile, 1.f);
		}
	}
	// *******
	// Layer 2
	// *******
	
	for(i = 0; i < MeshCount; i++)
	{
		tile_t *tile = tmp_meshlist[i]->tile;
				
		if(IsFloor(tile))
		{
			for(k = 0; k < 4; k++)
			{
				if(IsWall(tile, k))
				{
					DrawWall(tile, k, .85f);
				}
				if(IsCorner(tile, k))
				{
					DrawCorner(tile, k, .85f);
				}
			}
			DrawObjects(tile);
		}
	}
	// *******
	// Layer 3
	// *******
	
	for(i = 0; i < MeshCount; i++)
	{
		tile_t *tile = tmp_meshlist[i]->tile;
		
		if(!IsFloor(tile))
		{
			DrawFloor(tile, .85f);
		}
	}
}

// **************************************************************************************
// **************************************************************************************
// *** Private Initialization Functions
// **************************************************************************************
// **************************************************************************************

static void SubdivideEdge(vec2 v[], int skip)
{
	v[skip*3] = vec2::hmidpoint(v[0], v[skip*6]);
	v[skip*2] = vec2::hmidpoint(v[0], v[skip*3]);
	v[skip*1] = vec2::hmidpoint(v[0], v[skip*2]);
	v[skip*4] = vec2::hmidpoint(v[skip*6], v[skip*3]);
	v[skip*5] = vec2::hmidpoint(v[skip*6], v[skip*4]);
}

static void CalcMTV(mat3 m[16], vec2 t[49], vec2 v[49])
{
	int i, j;
	
	// *****************************************
	// Compute matrices used in tiling traversal
	// *****************************************
	
	float x1 = TileLength*2.f;
	float x2 = TileLength*TileLength;
	
	m[0] = mat3(1.f+x2, 0.f, -x1, 0.f, 1.f-x2, 0.f, -x1, 0.f, 1.f+x2);
	m[1] = mat3(1.f-x2, 0.f, 0.f, 0.f, 1.f+x2, +x1, 0.f, +x1, 1.f+x2);
	m[2] = mat3(1.f+x2, 0.f, +x1, 0.f, 1.f-x2, 0.f, +x1, 0.f, 1.f+x2);
	m[3] = mat3(1.f-x2, 0.f, 0.f, 0.f, 1.f+x2, -x1, 0.f, -x1, 1.f+x2);
	
	for(j = 0; j < 12; j += 4)
	{
		for(i = 0; i < 4; i++)
		{
			m[i+j+4] = mat3(m[i+j].x.y, -m[i+j].x.x, m[i+j].x.z,
			                m[i+j].y.y, -m[i+j].y.x, m[i+j].y.z,
			                m[i+j].z.y, -m[i+j].z.x, m[i+j].z.z);
		}
	}
	// **********************
	// Compute texture coords
	// **********************
	
	static float tcomp[7] = {0.f, .125f, .25f, .5f, .75f, .875f, 1.f};
	
	for(j = 0; j < 7; j++)
	{
		for(i = 0; i < 7; i++)
		{
			t[7*j+i] = vec2(tcomp[i], tcomp[j]);
		}
	}
	// ***********************************
	// Compute untransformed tile vertices
	// ***********************************
	// 
	//	 6--13--20------27------34--41--48
	//	 |   |   |       |       |   |   |
	//	 5--12--19------26------33--40--47
	//	 |   |   |       |       |   |   |
	//	 4--11--18------25------32--39--46
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 3--10--17------24------31--38--45
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 |   |   |       |       |   |   |
	//	 2---9--16------23------30--37--44
	//	 |   |   |       |       |   |   |
	//	 1---8--15------22------29--36--43
	//	 |   |   |       |       |   |   |
	//	 0---7--14------21------28--35--42
	
	v[0]  = vec2(-TileLength, -TileLength);
	v[6]  = vec2(-TileLength, +TileLength);
	v[42] = vec2(+TileLength, -TileLength);
	v[48] = vec2(+TileLength, +TileLength);
	
	SubdivideEdge(v+0,  7);
	SubdivideEdge(v+6,  7);
	SubdivideEdge(v+0,  1);
	SubdivideEdge(v+42, 1);
	
	for(i = 7; i < 42; i += 7)
	{
		SubdivideEdge(v + i, 1);
	}
}

// **************************************************************************************
// **************************************************************************************
// *** Public Functions
// **************************************************************************************
// **************************************************************************************

static int IsNeighborFree(tile_t *tile)
{
	int k;

	for(k = 0; k < 4; k++)
	{
		tile_t *next = tile->next[k];
		
		if(next && IsFree(next)) return 1;
	}
	return 0;
}

void HyperTile_Init(int size)
{
	int i, k;
	
	CalcMTV(MESH__M, MESH__T, MESH__V);
	
	// ***************
	// Generate tiling
	// ***************
	
	int total;
	
	AddTile(NULL);
	
	for(i = total = 1; i < size+4; i++)
	{
		int j, count = TileCount;
		
		for(j = 0; j < count; j++)
		{
			tile_t *tile = &TileList[j];
			
			for(k = 0; k < 4; k++)
			{
				if(!tile->next[k])
				{
					tile_t *next  = AddTile(tile);
					tile->next[k] = next;
					CurlLeft(next);
					CurlRight(next);
				//	total++;
					
					if(i >= size) ClearFree(next);
					else total++;
				}
			}
		}
	}
	// *************
	// Generate maze
	// *************
	
	int count;
	int max = total / 2;
	
	tile_t *tile, *next, *path[1000];
	int pathlen = 0;
	int pathcount;
	
	tile = &TileList[0];
	ClearFree(tile);
	
	for(count = 1; count < max;)
	{
		// ************************************************
		// Find a visited tile with a non-visited neighbor:
		// First, try to branch off previous path
		// ************************************************
		
		// **************************************
		// If branch fails, find some other point
		// **************************************
		
		do
		{
			static int j = 0;
			
			do
			{
				j++; if(j == total) j = 0;
				tile = &TileList[j];
			}
			while(IsFree(tile));
		}
		while(!IsNeighborFree(tile));
		
		// **************
		// Construct path
		// **************
		
		pathlen   = 0;
		pathcount = rand() % 128 + 1;
		
		for(; count < max && pathlen < pathcount; count++)
		{
			if(rand() % 20 == 0) i++; // change direction
			
			for(k = 0; k < 4; k++)
			{
				next = tile->next[(i+k)&3];
				
				if(next && IsFree(next)) break;
			}
			if(k == 4) break; // end of path
			
			i = (i+k)&3;
			path[pathlen++] = next;
			ClearWall(tile, i);
			i = FindIndex(tile, next);
			ClearWall(next, i);
			ClearFree(next);
			tile = next;
			i = (i+2)&3;
		}
	}
	// ******************************
	// Remove "dangling" border tiles
	// ******************************
	
	int removed = 0;
	
	if(1) for(i = 0; i < TileCount; i++)
	{
		tile = &TileList[i];
		
		int b = 0;
		
		for(k = 0; k < 4; k++)
		{
			if(!tile->next[k]) b++;
			else next = tile->next[k];
		}
		if(b == 3)
		{
			SetFree(tile);
			
			b = FindIndex(tile, next);
			
			next->next[b] = NULL;
			SetWall(next, b);
			
			removed++;
		}
	}
	// ***********
	// Count walls
	// ***********
	
	int wallcount = 0;
	
	for(i = 0; i < TileCount; i++)
	{
		tile = &TileList[i];
		
		if(IsFree(tile)) continue;
		
		for(k = 0; k < 4; k++)
		{
			if(tile->next[k] && IsWall(tile, k))
			{
				wallcount++;
			}
		}
	}
	wallcount /= 2;
	
	// ****************************************
	// Remove some more walls to make it harder
	// ****************************************
	
	if(1) for(i = 0; i < 1; i++)
	{
		do
		{
			do
			{
				tile = &TileList[rand() % TileCount];
			}
			while(IsFree(tile));
			
			for(k = 0; k < 4; k++)
			{
				next = tile->next[k];
				
				if(next && IsWall(tile, k)) break;
			}
		}
		while(k == 4);
		
		ClearWall(tile, k);
		ClearWall(next, FindIndex(tile, next));
	}
	// ****************
	// Set corner flags
	// ****************
	
	for(i = 0; i < TileCount; i++)
	{
		tile = &TileList[i];
		
		if(IsFree(tile)) continue;
		
		for(k = 0; k < 4; k++)
		{
			if(!IsWall(tile, k) && !IsWall(tile, (k-1)&3))
			{
				tile_t *tile2 = tile;
				int k2 = k;
				
				do
				{
					if(IsWall(tile2, k2))
					{
						SetCorner(tile, k);
						break;
					}
					next = tile2->next[k2];
					k2 = (FindIndex(tile2, next) + 1)&3;
					tile2 = next;
				}
				while(tile2 != tile);
			}
		}
	}
	// ************************
	// Set non-floor tile flags
	// ************************
	
	removed = 0;
	
	if(1) for(i = 0; i < TileCount; i++)
	{
		tile = &TileList[i];
		
		if(IsWall(tile, 0) && IsWall(tile, 1) &&
		   IsWall(tile, 2) && IsWall(tile, 3) )
		{
			ClearWall(tile, 0);
			ClearWall(tile, 1);
			ClearWall(tile, 2);
			ClearWall(tile, 3);
			ClearFloor(tile);
			removed++;
		}
	}
	TileCount -= removed;
}

/*
long HyperTile_InitHW(long window)
{
	// *****************
	// Create a fog mask
	// *****************
	
	short *fogmask;
	int x, y;
	int area = SCREEN_RES_X*SCREEN_RES_Y;
	
	fogmask = (short*)malloc(sizeof(short)*area);
	
	for(y = 0; y < SCREEN_RES_Y; y++)
	{
		for(x = 0; x < SCREEN_RES_X; x++)
		{
			vec2 v = vec2((float)(x-SCREEN_RES_X/2),
			              (float)(y-SCREEN_RES_Y/2));
			
			v /= (float)(SCREEN_RES_Y/2);
			
			float fog  = 1.f - (float)pow(v*v, 3.f);
			short fogi = (short)(fog*255.f);
			
			if(fogi <   0) fogi =   0;
			if(fogi > 255) fogi = 255;
			
		//	fogi = 255; // no fog
			
			if(v*v > 1.f) fogi = 0;
			
			fogmask[x + y*SCREEN_RES_X] = fogi;
		}
	}
	// **************************************
	// Initialize 3Dfx Glide rendering system
	// **************************************
	
	long context = 1;
	
#if RENDER
	
	grGlideInit();
	grSstSelect(0);
	context = (long)grSstWinOpen(
		window,
		GR_RESOLUTION_800x600,
		GR_REFRESH_60Hz,
		GR_COLORFORMAT_RGBA,
		GR_ORIGIN_LOWER_LEFT, 2, 1);
	if(!context) return 0;
	
	// *************************************
	// Configure Glide to receive alpha mask
	// *************************************
	
	grCoordinateSpace(GR_WINDOW_COORDS);
	grViewport(0, 0, SCREEN_RES_X, SCREEN_RES_Y);
	grVertexLayout(GR_PARAM_XY, 0, GR_PARAM_ENABLE);
	grVertexLayout(GR_PARAM_W, 8, GR_PARAM_ENABLE);
	grVertexLayout(GR_PARAM_A, 12, GR_PARAM_ENABLE);
	grDepthBufferMode(GR_DEPTHBUFFER_DISABLE);
	grDepthMask(FXFALSE);
	grColorMask(FXTRUE, FXTRUE);
	grAlphaCombine(
		GR_COMBINE_FUNCTION_LOCAL,
		GR_COMBINE_FACTOR_ONE,
		GR_COMBINE_LOCAL_ITERATED,
		GR_COMBINE_OTHER_NONE,
		FXFALSE);
	grAlphaBlendFunction(
		GR_BLEND_ZERO,
		GR_BLEND_ZERO,
		GR_BLEND_ONE,
		GR_BLEND_ZERO);
	
	// *****************************
	// Copy fog mask to alpha buffer
	// *****************************
	
	for(y = 0; y < SCREEN_RES_Y; y++)
	{
		for(x = 0; x < SCREEN_RES_X; x++)
		{
			struct vtmp_t
			{
				float x, y, z, a;
			};
			vtmp_t v;
			
			v.x = (float)x;
			v.y = (float)y;
			v.z = 1.f;
			v.a = (float)fogmask[x + y*SCREEN_RES_X];
			
			RenderPoint(&v);
		}
	}
	// *****************************
	// Configure Glide for rendering
	// *****************************
	
	grCoordinateSpace(GR_CLIP_COORDS);
	grVertexLayout(GR_PARAM_A, -1, GR_PARAM_DISABLE);
	grVertexLayout(GR_PARAM_RGB, 12, GR_PARAM_ENABLE);
	grVertexLayout(GR_PARAM_ST0, 24, GR_PARAM_ENABLE);
	grViewport(
		(SCREEN_RES_X-SCREEN_RES_Y)/2, 0,
		 SCREEN_RES_Y,SCREEN_RES_Y);
	grTexClampMode(
		GR_TMU0,
		GR_TEXTURECLAMP_WRAP,
		GR_TEXTURECLAMP_WRAP);
	grTexFilterMode(
		GR_TMU0,
		GR_TEXTUREFILTER_BILINEAR,
		GR_TEXTUREFILTER_BILINEAR);
	grTexMipMapMode(
		GR_TMU0,
		GR_MIPMAP_NEAREST_DITHER,
		FXFALSE);
	grEnable(GR_ALLOW_MIPMAP_DITHER); // slow, but we'll use it for now
	grTexCombine(
		GR_TMU0,
		GR_COMBINE_FUNCTION_LOCAL,
		GR_COMBINE_FACTOR_NONE,
		GR_COMBINE_FUNCTION_LOCAL,
		GR_COMBINE_FACTOR_NONE,
		FXFALSE,
		FXFALSE);
	grColorCombine(
		GR_COMBINE_FUNCTION_LOCAL,
		GR_COMBINE_FACTOR_LOCAL,
		GR_COMBINE_LOCAL_ITERATED,
		GR_COMBINE_OTHER_NONE,
		FXFALSE);
	grAlphaBlendFunction(
		GR_BLEND_DST_ALPHA,
		GR_BLEND_ZERO,
		GR_BLEND_ZERO,
		GR_BLEND_ZERO);
	grColorMask(FXTRUE, FXFALSE);
	Render_LoadTexture("STONE01.3df");
#endif
	
	return (long)context;
}
*/

void HyperTile_Draw(mat3 &m, float scale, float morph, int drop)
{
	static obj_t player;
	static obj_t ghost;
	
	static int inited = 1;
	
	// *******************
	// Place player object
	// *******************
	
	if(inited)
	{
		inited = 0;
		
		player.flags  = OBJ_FLAG_PLAYER | OBJ_FLAG_MOVING | OBJ_FLAG_CAMERA;
		player.m.identity();
		player.m_inv  = player.m;
		player.r      = .2f;
		player.r_max  = .2f;
		player.next   = NULL;
		player.color1 = vec3(0.f, 1.f, 0.f);
		player.color2 = vec3(0.f, 0.f, 1.f);
		player.verts  = 16;
		
		ghost = player;
		ghost.flags = OBJ_FLAG_GHOST | OBJ_FLAG_MOVING | OBJ_FLAG_CAMERA;
		
		PlaceObj(&player, TileList);
		PlaceObj(&ghost,  TileList);
	}
	// ***************************
	// Place dropped object marker
	// ***************************
	
	if(drop && ObjectCount < MAX_WORLD_OBJECTS)
	{
		obj_t *obj = &ObjectList[ObjectCount++];
		
		obj->flags  = 0;
		obj->m      = player.m;
		obj->m_inv  = player.m_inv;
		obj->r      = 0.f;
		obj->r_max  = vec3::random().x * .1f + .1f;
		obj->next   = NULL;
		obj->color1 = vec3::random().normalized() * .5f;
		obj->color2 = vec3::random().normalized();
		obj->verts  = (rand() % 5) + 3;
		
		PlaceObj(obj, player.tile);
	}
	// *********************************
	// Update scaling and visible radius
	// *********************************
	
	float q = scale*scale + morph*morph;
	
	ProjectionScale = scale;
	ProjectionMorph = morph;
	LimR = (scale + morph*(float)sqrt(q - 1.f)) / q;
	
	// ***********************************
	// Update player transform and physics
	// ***********************************
	
	// NOTE: Updating player object initializes the first
	// tile for rendering. No subsequent objects should be
	// updated between updating the player and drawing
	// the tiling.
	
	if(player.tile->frame_visited < 1)
	{
		player.tile->frame_visited = CurrentFrame;
		TILES_HIT++;
	}
	// *******************
	// Draw "outside area"
	// *******************
	if(0)
	{
		vertex_t v[4];
		
		v[0].v = vec2(-1.f, -1.f);
		v[1].v = vec2(+1.f, -1.f);
		v[2].v = vec2(+1.f, +1.f);
		v[3].v = vec2(-1.f, +1.f);
		v[0].c = v[1].c = v[2].c = v[3].c = vec3(.7f, 1.f, .2f);
#if RENDER
		/* 3Dfx Glide
		grColorCombine(
			GR_COMBINE_FUNCTION_LOCAL,
			GR_COMBINE_FACTOR_NONE,
			GR_COMBINE_LOCAL_ITERATED,
			GR_COMBINE_OTHER_NONE,
			FXFALSE);
			*/
#endif
		RenderPoly(v, 4);
	}
	// **********************
	// Draw tiles and objects
	// **********************
	
	CurrentFrame++;
	
#if RENDER
	/* 3Dfx Glide
	grColorCombine(
		GR_COMBINE_FUNCTION_SCALE_OTHER_ADD_LOCAL,
		GR_COMBINE_FACTOR_ONE,
		GR_COMBINE_LOCAL_ITERATED,
		GR_COMBINE_OTHER_TEXTURE,
		FXFALSE);
		*/
	float tec[] = {1.f, 1.f, 1.f, 1.f};
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_BLEND);
	glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, tec);
#endif
	DrawScene(&ghost);
	
	// 3Dfx Glide
	//int sync = GetSwapSync();
	int sync = 1;
	
	while(sync--)
	{
		UpdateObj(&player, m*player.m);	
#if 1
		if(player.tile == ghost.tile)
	//	if(1)
		{
		mat3 target = ghost.tile->mesh->m * player.tile->mesh->m.inverse() * player.m;
		target /= target.z.z;
		ghost.m /= ghost.m.z.z;
		UpdateObj(&ghost, ghost.m*.95f + target*.05f);
		}
		else
		{
		vec2 target = player.m_inv * vec2(0.f, 0.f);
		target = player.tile->mesh->m * target;
		target *= -.05f;
		UpdateObj(&ghost, mat3::htranslate(target)*ghost.m);
		
	//	float theta1, theta2;
	//	vec2  trans1, trans2;
	//	mat3::hdecompose(theta1, trans1, ghost.m);
	//	mat3::hdecompose(theta2, trans2, player.m);
	//	theta1 = .98f*theta1 + .02f*theta2;
	//	trans1 = trans1*.98f + trans2*.02f;
	//	UpdateObj(&ghost, mat3::htranslate(trans1)*mat3::rotZ(theta1));
		}
#else
		ghost.m = player.m;
		ghost.tile = player.tile;
#endif
	}
	// Draw FOG MASK (OpenGL)
	if(1)
	{
		glDisable(GL_DEPTH_TEST);
		glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
		glColor3f(1.f, 1.f, 1.f);
		glEnable(GL_BLEND);
		glBlendFunc(GL_ZERO, GL_SRC_COLOR);
		glBindTexture(GL_TEXTURE_2D, MAIN__texid[0]);
		glBegin(GL_POLYGON);
		glTexCoord2f(-.15f, -.15f); glVertex3f(-1.f, -1.f, 0.4f);
		glTexCoord2f(1.15f, -.15f); glVertex3f( 1.f, -1.f, 0.4f);
		glTexCoord2f(1.15f, 1.15f); glVertex3f( 1.f,  1.f, 0.4f);
		glTexCoord2f(-.15f, 1.15f); glVertex3f(-1.f,  1.f, 0.4f);
		glEnd();
		glEnable(GL_DEPTH_TEST);
		glDisable(GL_BLEND);
	}
	// *****************
	// Draw progress bar
	// *****************
	if(1)
	{
		vertex_t v[4];
		
		v[0].v = vec3(-1.f, -.94f,1.1f);
		v[1].v = vec3(+1.f, -.94f,1.1f);
		v[2].v = vec3(+1.f, -1.f, 1.1f);
		v[3].v = vec3(-1.f, -1.f, 1.1f);
		v[0].c = v[1].c = v[2].c = v[3].c = vec3(0.f, 0.f, .5f);
		
#if RENDER
		/* 3Dfx Glide
		grAlphaBlendFunction(
			GR_BLEND_ONE,
			GR_BLEND_ZERO,
			GR_BLEND_ZERO,
			GR_BLEND_ZERO);
		grColorCombine(
			GR_COMBINE_FUNCTION_LOCAL,
			GR_COMBINE_FACTOR_NONE,
			GR_COMBINE_LOCAL_ITERATED,
			GR_COMBINE_OTHER_NONE,
			FXFALSE);
			*/
#endif
		glDisable(GL_DEPTH_TEST);
		glDisable(GL_TEXTURE_2D);
		RenderPoly(v, 4);
		if(TileCount-2 == TILES_HIT)
		{
			v[0].c = v[1].c = v[2].c = v[3].c = vec3::random();
		}
		else
		{
			v[0].c = v[1].c = v[2].c = v[3].c = vec3(0.f, 0.f, 1.f);
		}
		v[1].v.x = v[2].v.x = -1.f + 2.f*(float)TILES_HIT/(float)(TileCount-2);
		RenderPoly(v, 4);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_TEXTURE_2D);
#if RENDER
		/* 3Dfx Glide
		grAlphaBlendFunction(
			GR_BLEND_DST_ALPHA,
			GR_BLEND_ZERO,
			GR_BLEND_ZERO,
			GR_BLEND_ZERO);
			*/
#endif
	}
#if RENDER
	/* 3Dfx Glide
	grBufferSwap(1);
	*/
#endif
}

// **************************************************************************************
// **************************************************************************************
// *** Main Program and GLUT Stuff
// **************************************************************************************
// **************************************************************************************

static void GLUT__Display(void)
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	MAIN__RenderScene();
	MAIN__TrackFPS();
	{
		static vec2 vel(0.f, 0.f);
		static float scale = 1.4f; // 1.4 = 33tiles, 1.2 = 54tiles, 1.1 = 105tiles
		static float morph = 1.0f;
		
		static float maxvl = .0650f;
		static float accel = .0030f;
		
		static int drop_counter = 0;
		int drop = 0;
		
		if(GetAsyncKeyState(VK_SPACE) & 0x8000)
		{
			if(drop_counter > 15)
			{
				drop = 1;
				drop_counter = 0;
			}
		}
		drop_counter++;
		
		float m_old = morph;
		
		vel *= .9f;
		if(GetAsyncKeyState(VK_NUMPAD4) & 0x8000) vel.x += accel;
		if(GetAsyncKeyState(VK_NUMPAD6) & 0x8000) vel.x -= accel;
		if(GetAsyncKeyState(VK_NUMPAD2) & 0x8000) vel.y += accel;
		if(GetAsyncKeyState(VK_NUMPAD8) & 0x8000) vel.y -= accel;
		if(GetAsyncKeyState(VK_UP)      & 0x8000) scale += .01f;
		if(GetAsyncKeyState(VK_DOWN)    & 0x8000) scale -= .01f;
		if(GetAsyncKeyState(VK_LEFT)    & 0x8000) morph += .01f;
		if(GetAsyncKeyState(VK_RIGHT)   & 0x8000) morph -= .01f;
		
		if(morph < 0.00f) morph = 0.00f;
		if(morph > 1.00f) morph = 1.00f;
		
		if(morph != m_old)
		{
			scale *= 1.f + morph*(float)sqrt(.75f);
			scale /= 1.f + m_old*(float)sqrt(.75f);
		}
		if(scale < 1.02f) scale = 1.02f;
		if(scale > 2.00f) scale = 2.00f;
		
		if(vel.len() > maxvl) vel *= maxvl / vel.len();
		
		HyperTile_Draw(mat3::htranslate(vel), scale, morph, drop);
	}
	glFlush();
    glutSwapBuffers();
}

static void GLUT__Reshape(int width, int height)
{
	int offset_x = 0;
	int offset_y = 0;
	
	if(width > height)
	{
		offset_x = (width - height)/2;
		width = height;
	}
	else
	{
		offset_y = (height - width)/2;
		height = width;
	}
//	float aspect = (float)height / (float)width;
	float aspect = 1.0f;
	
	glViewport(offset_x, offset_y, width, height);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glScalef(1.f, 1.f, -1.f);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluPerspective(60.f, 1.f/aspect, .001f, 100.f);
	glTranslatef(0.f, 0.f, -1.f);
}

static void GLUT__Keyboard(unsigned char key, int x, int y)
{
	if(key == 27) MAIN__STOP = 1; // ESC
}

static void GLUT__Mouse(int button, int state, int x, int y)
{
}

static void GLUT__Idle(void)
{
	if(WAIT_FOR_CLOCK) Sleep(8);
	glutPostRedisplay();
}

void main(int argc, char *argv[])
{
	fprintf(stdout, "HypEngine: Hyperbolic Maze Demo\n");
	fprintf(stdout, "(c) Bernie Freidin, 1999-2000\n\n");
	fprintf(stdout, "Keys:\n");
	fprintf(stdout, "  Numeric keypad to move (NumLock ON)\n");
	fprintf(stdout, "  Arrow keys to zoom in/out and adjust view\n");
	fprintf(stdout, "  SPACE to drop an object\n");
	fprintf(stdout, "  ESCAPE to quit\n\n");
	fprintf(stdout, "Tips:\n");
	fprintf(stdout, "  Zoom in and adjust view RIGHT for faster framerate\n");
	fprintf(stdout, "  When maze is fully explored, status bar flashes\n");
	
	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(640, 480);
	glutInitWindowPosition(40, 40);
	glutCreateWindow(argv[0]);
	glutDisplayFunc(GLUT__Display);
	glutReshapeFunc(GLUT__Reshape);
	glutKeyboardFunc(GLUT__Keyboard);
	glutMouseFunc(GLUT__Mouse);
	glutIdleFunc(GLUT__Idle);
	
	const unsigned char *ext = glGetString(GL_EXTENSIONS);
	
	glClearColor(0.f, 0.f, 0.f, 1.0f);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable(GL_TEXTURE_2D);
	glEnable(GL_DEPTH_TEST);
	
	glGenTextures(MAIN__texcount, MAIN__texid);
	
	for(int i = 0; i < MAIN__texcount; i++)
	{
		glBindTexture(GL_TEXTURE_2D, MAIN__texid[i]);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		
		if(i > 0)
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
			MAIN__LoadTGA(MAIN__texnames[i-1]);
		}
		else
		{
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
			MAIN__LoadAlphaMask(512);
		}
	}
	HyperTile_Init(7); // 7
	// 3Dfx Glide
	//context = HyperTile_InitHW((long)hwnd);
	//if(!context) return;
	
	glutMainLoop();
}

/*
COMPUTING "TileLength"...

hdist[v1_,v2_] = (1-v1.v2)/Sqrt[(1-v1.v1)(1-v2.v2)];
p0 =  {0,0};
p1 = a{1,0};
p2 = a{Cos[2Pi/5],Sin[2Pi/5]};
p3 = b{Cos[1Pi/5],Sin[1Pi/5]};
Solve[{
hdist[p0,p3] - hdist[p1,p2] == 0,
hdist[p0,p1] - hdist[p1,p3] == 0}, {a, b}]
:a is the Euclidean length along an edge
:b is the Euclidean length along a diagonal
(assuming the square's corner is resting at 0,0)
*/

This page © Bernie Freidin, 2000.