Zoom! Demo Source

by Bernie Freidin © 1999-2000



pack.cpp main.cpp menu.cpp grs.cpp
pack.h zoom.h menu.h grs.h


// -------------------------------------
// ZOOM: Fractal Circle Packing Explorer
// (c) Bernie Freidin, 1999
// -------------------------------------

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <gl/glut.h>
#include "zoom.h"
#include "pack.h"
#include "menu.h"

#include "grs.h"

//NOTE: icon bar is disabled for now.. stylish though
#define ICON_BAR 32 // icon bar width
#define STAT_BAR 56 // status bar height

// ********************************
// Zoom stack and window resolution
// ********************************

#define MAX_ZOOMS 100

static zoom_t ZOOM_LIST[MAX_ZOOMS], Z;
static int    ZOOM_COUNT = 0;
static int    ZOOM_STATE = 0;
static int    ZOOM_HILITE = 0;
static double ZOOM_MAX_TOTAL;
static double ZOOM_MAX_SINGLE;
static double ZOOM_VEL_LINEAR;
static double ZOOM_VEL_NONLINEAR;
static double SCROLL_VEL_2D;

// **************
// Set by RESHAPE
// **************

static int    WINDOW_X;
static int    WINDOW_Y;
static double VIEW_X;
static double VIEW_Y;
static double VIEW_ASPECT;

// ***************************
// Interface to Circle Packing
// ***************************

static packinfo_t PACKINFO;

// *********
// GRS stuff
// *********

static grs_t GRS = {NULL};
static int GRS_MODE = 0;

static char GRS_presetNames[][60] = {
	"birdhead.grs",
	"bronto.grs",
	"dragon.grs",
	"house.grs",
	"knight.grs",
	"rex.grs",
	"scene.grs",
	"usa.grs",
	"vinci.grs"
};

// **********************
// Common render settings
// **********************

static int    ANTI_ALIASED = 0;
static double LINE_WIDTH   = 1.0;

// ********************
// Local menu functions
// ********************

static void _mainMenuCallback(int value)
{
	if(value == 1) exit(0);
}

static void GLUT__Display(void);

static void _advancedMenuCallback(int value)
{
	if(value == 1)
	{
		//FIXME: broken!
		PACK_ClearTSPMemory();
		glutPostRedisplay();
	}
}

static void _currentObjectCallback(int value)
{
	if(value == 1)
	{
		GRS_MODE = 0;
		ZOOM_LIST[0].x = 0.0;
		ZOOM_LIST[0].y = 0.0;
		ZOOM_LIST[0].z = 0.95;
		ZOOM_COUNT = 1;
		ZOOM_STATE = 0;
		Z = ZOOM_LIST[0];
		glutPostRedisplay();
	}
}

static void _GRS_loadCallback(int value)
{
	char buf[200];
	
	if(value < 10)
	{
		strcpy(buf, "GRS Data/");
		strcat(buf, GRS_presetNames[value-1]);
	}
	else
	{
		fprintf(stdout, "Enter GRS path: ");
		fgets(buf, 199, stdin);
		*strchr(buf, 10) = '\0'; //!!
	}
	if(GRS_Load(&GRS, buf) < 0) return;
	GRS_MODE = 1;
	ZOOM_LIST[0].x = 0.0;
	ZOOM_LIST[0].y = 0.0;
	ZOOM_LIST[0].z = 0.95;
	ZOOM_COUNT = 1;
	ZOOM_STATE = 0;
	Z = ZOOM_LIST[0];
	glutPostRedisplay();
}

static void InitializeMenus(void)
{
	// *********************************************
	// Initialize menus. The reason for placing this
	// function in MAIN.CPP is so that the menus may
	// be linked to the variables that they affect.
	// It's ugly, I know!!
	// *********************************************
	
	int m0 = glutCreateMenu(_mainMenuCallback);
	int m1 = glutCreateMenu(NULL);
	int m2 = glutCreateMenu(_advancedMenuCallback);
	int m4 = glutCreateMenu(_currentObjectCallback);
	int m5 = glutCreateMenu(_GRS_loadCallback);
	
	glutSetMenu(m0);
	glutAddSubMenu("Current Object", m4);
	glutAddSubMenu("Rendering", m1);
	glutAddSubMenu("Advanced", m2);
	glutAddMenuEntry("Exit", 1);
	glutAttachMenu(GLUT_RIGHT_BUTTON);
	
	// **************************************
	// Register sub-menus with handy function
	// **************************************
	
	glutSetMenu(m4);
	glutAddMenuEntry("Circle Packing", 1);
	glutSetMenu(m5);
	
	for(int i = 0; i < 9; i++)
	{
		glutAddMenuEntry(GRS_presetNames[i], i+1);
	}
	glutAddMenuEntry("Load Custom GRS...", 10);
	
	glutSetMenu(m4);
	glutAddSubMenu("GRS", m5);
	
	MENU_Register(-1, "",
	0.0, 1.0, 1.0, "Anti-Aliasing", m1, 1, &ANTI_ALIASED);
	MENU_Register(4, "1 1.5 2 3",
	0.5, 20.0, 1.5, "Line Width", m1, 2, &LINE_WIDTH);
	
	MENU_Register(6, "10 50 100 200 500 1000",
	5.0, 5000.0, 500.0, "Packing LOD", m2, 1, &PACKINFO.max_render_z);
	MENU_Register(6, "10 50 100 200 500 1000",
	5.0, 5000.0, 500.0, "Object LOD", m2, 2, &PACKINFO.lod_constant);
	MENU_Register(3, "-0.66 -0.87 -0.99",
	-1.0, -0.01, -0.66, "Fade Exponent", m2, 3, &PACKINFO.fade_exponent);
	MENU_Register(1, "300000",
	100.0, 1000000000.0, 300000.0, "Max Total Zoom", m2, 4, &ZOOM_MAX_TOTAL);
	MENU_Register(1, "20",
	2.0, 1000.0, 20.0, "Max Single Zoom", m2, 5, &ZOOM_MAX_SINGLE);
	MENU_Register(-1, "",
	0.0, 1.0, 1.0, "Dynamic Circle Generation", m2, 6, &PACKINFO.dynamic);
	MENU_Register(-1, "",
	0.0, 1.0, 0.0, "Dynamic C. Gen. In Shadow", m2, 7,
	&PACKINFO.dynamic_shadow);
	MENU_Register(1, "0.2",
	0.0, 1.0, 0.2, "2D Scroll Speed", m2, 8, &SCROLL_VEL_2D);
	MENU_Register(-1, "",
	0.0, 1.0, 0.0, "Show [x,y,z] Components", m2, 9, &PACKINFO.show_vec_3);
	MENU_Register(-1, "",
	0.0, 1.0, 0.0, "Show Depth Component", m2, 10, &PACKINFO.show_depth);
	
	int m3 = glutCreateMenu(NULL);
	MENU_Register(1, "0.1",
	0.0, 1.0, 0.1, "Linear Velocity", m3, 1, &ZOOM_VEL_LINEAR);
	MENU_Register(1, "0.01",
	0.0, 1.0, 0.01, "Nonlinear Velocity", m3, 2, &ZOOM_VEL_NONLINEAR);
	glutSetMenu(m2);
	glutAddSubMenu("Zoom Velocity Coefficients", m3);
	glutAddMenuEntry("Clear TSP Memory (BROKEN)", 1);
}

static void SetPixelViewport(int lft, int top, int rgt, int bot)
{
	// ************************************
	// Set up a pixel-unit coordinate space
	// and clear it to the current color.
	// ************************************
	
	if(lft < 0) lft = WINDOW_X + lft;
	if(top < 0) top = WINDOW_Y + top;
	if(rgt < 0) rgt = WINDOW_X;
	if(bot < 0) bot = WINDOW_Y;
	
	glViewport(lft, top, rgt, bot);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	glTranslated(-1.0, -1.0, 0.0);
	glScaled(2.0/(double)(rgt-lft), 2.0/(double)(bot-top), 1.0);
	glRecti(0, 0, rgt-lft, bot-top); // clear
}

static void DrawText(int x, int y, char *str)
{
	// **************************
	// Draw a dumb string of text
	// **************************
	
	glRasterPos2i(x, y);
	
	while(*str != '\0')
	{
		glutBitmapCharacter(GLUT_BITMAP_8_BY_13, *(str++));
	}
}

static void DrawStatus(void)
{
	char buf[200];
	
	// **********
	// Save state
	// **********
	
	int vsave[4];
	glGetIntegerv(GL_VIEWPORT, vsave);
	glMatrixMode(GL_MODELVIEW);
	glPushMatrix();
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
	
	// **********
	// Status bar
	// **********
	
	double per = 100.0 * (double)PACKINFO.obj_in_view /
	                     (double)PACKINFO.obj_total;
	
	glColor3d(0.2, 0.2, 0.4);
	SetPixelViewport(ICON_BAR, 0, -1, STAT_BAR);
	glColor3d(0.5, 1.0, 0.5);
	
	sprintf(buf, "scaling=  %6.4f", Z.z);
	DrawText(10, 10, buf);
	sprintf(buf, "position= %6.4f, %6.4f", Z.x*Z.z, Z.y*Z.z);
	DrawText(10, 26, buf);
	
	if(!GRS_MODE)
	{
		sprintf(buf, "total objects=  %i", PACKINFO.obj_total);
		DrawText(300, 42, buf);
		sprintf(buf, "drawn objects=  %i (%.3f%%)", PACKINFO.obj_in_view, per);
		DrawText(300, 26, buf);
		sprintf(buf, "nodes rejected= %i", PACKINFO.node_reject);
		DrawText(300, 10, buf);
	}
	else
	{
		sprintf(buf, "GRS file name= %s", GRS.filename);
		DrawText(300, 42, buf);
		sprintf(buf, "num polylines= %i", GRS.polylinecount);
		DrawText(300, 26, buf);
		sprintf(buf, "num line segs= %i", GRS.linecount);
		DrawText(300, 10, buf);
	}
	// ********
	// Icon bar
	// ********
	
	glColor3d(0.2, 0.2, 0.4);
	SetPixelViewport(0, 0, ICON_BAR, -1);
	
	// *************
	// Restore state
	// *************
	
	glViewport(vsave[0], vsave[1], vsave[2], vsave[3]);
	
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
	glMatrixMode(GL_MODELVIEW);
	glPopMatrix();
}

static void Zoom(zoom_t *zoom)
{
	// *********************************
	// Convert to normalized coordinates
	// *********************************
	
	double x1 = +(2.0*(double)Z.box[0]/VIEW_X - 1.0);
	double y1 = -(2.0*(double)Z.box[1]/VIEW_Y - 1.0);
	double x2 = +(2.0*(double)Z.box[2]/VIEW_X - 1.0);
	double y2 = -(2.0*(double)Z.box[3]/VIEW_Y - 1.0);
	x1 *= VIEW_ASPECT;
	x2 *= VIEW_ASPECT;
	if(x1 > x2) {double x3 = x1; x1 = x2; x2 = x3;}
	if(y1 > y2) {double y3 = y1; y1 = y2; y2 = y3;}
	
	// *********************
	// Create zoom transform
	// *********************
	
	double z2;
	
	if(VIEW_X*(y2-y1) < VIEW_Y*(x2-x1))
		z2 = 2.0*VIEW_ASPECT/(x2-x1); else
		z2 = 2.0/(y2-y1);
	
	if(z2     > ZOOM_MAX_SINGLE) z2 = ZOOM_MAX_SINGLE;
	if(z2*Z.z > ZOOM_MAX_TOTAL)  z2 = ZOOM_MAX_TOTAL/Z.z;
	
	// ***************************
	// Concatenate zoom transforms
	// ***************************
	
	zoom->x = -0.5*(x1+x2)/Z.z + Z.x;
	zoom->y = -0.5*(y1+y2)/Z.z + Z.y;
	zoom->z = z2*Z.z;
}

static void GLUT__Display(void)
{
	glClear(GL_COLOR_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	
	if(ANTI_ALIASED) glEnable(GL_LINE_SMOOTH);
	else             glDisable(GL_LINE_SMOOTH);
	
	glLineWidth((float)LINE_WIDTH);
	
	if(ZOOM_STATE > 0 && ZOOM_STATE < 4)
	{
		if(Z.box[0] != Z.box[2] ||
		   Z.box[1] != Z.box[3] )
		{
			zoom_t zoom;
			Zoom(&zoom);
			
			// ********
			// Draw box
			// ********
			
			double x1 = +(2.0*(double)Z.box[0]/VIEW_X - 1.0);
			double y1 = -(2.0*(double)Z.box[1]/VIEW_Y - 1.0);
			double x2 = +(2.0*(double)(Z.box[2]-1)/VIEW_X - 1.0);
			double y2 = -(2.0*(double)(Z.box[3]+1)/VIEW_Y - 1.0);
			x1 *= VIEW_ASPECT;
			x2 *= VIEW_ASPECT;
			if(x1 > x2) {double x3 = x1; x1 = x2; x2 = x3;}
			if(y1 > y2) {double y3 = y1; y1 = y2; y2 = y3;}
			
			if(ZOOM_HILITE) glColor3d(1.0, 1.0, 0.0);
			else            glColor3d(0.4, 0.4, 0.0);
			
			glBegin(GL_LINE_LOOP);
			glVertex2d(x1, y1);
			glVertex2d(x2, y1);
			glVertex2d(x2, y2);
			glVertex2d(x1, y2);
			glEnd();
			
			// ***********
			// Draw shadow
			// ***********
			
			glScaled(zoom.z, zoom.z, 1.0);
			glTranslatef(zoom.x, zoom.y, 0.0);
			
			if(GRS_MODE)
			{
				glColor3d(0.3, 0.3, 0.5);
				GRS_Draw(&GRS);
			}
			else PACK_Draw(1, &zoom);
		}
	}
	// ******************
	// Draw current image
	// ******************

	glLoadIdentity();
	glScaled(Z.z, Z.z, 1.0);
	glTranslated(Z.x, Z.y, 0.0);
	
	if(GRS_MODE)
	{
		glColor3d(0.3, 0.3, 1.0);
		GRS_Draw(&GRS);
	}
	else PACK_Draw(0, &Z);
	
	// *****************
	// Draw status stuff
	// *****************
	
	DrawStatus();
	
	glFlush();
    glutSwapBuffers();
}

static void GLUT__Reshape(int width, int height)
{
	WINDOW_X = width;
	WINDOW_Y = height;
	VIEW_X = (double)(width  - ICON_BAR);
	VIEW_Y = (double)(height - STAT_BAR);
	VIEW_ASPECT = VIEW_X / VIEW_Y;
	
	PACK_SetView(VIEW_X, VIEW_Y);
	
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glScaled(VIEW_Y/VIEW_X, 1.0, 1.0);
	glViewport(ICON_BAR, STAT_BAR,
	           width  - ICON_BAR,
	           height - STAT_BAR);
	
	if(ZOOM_STATE != 4) ZOOM_STATE = 0;
}

static void GLUT__Keyboard(unsigned char key, int x, int y)
{
	if(key == 27) exit(0); // ESC key quits program
	
	// ******************************************
	// Backspace key restores previous zoom state
	// ******************************************
	
	if(key == 8)
	{
		if(ZOOM_COUNT > 1)
		{
			ZOOM_COUNT--;
			ZOOM_STATE = 4;
		}
	}
}

static void GLUT__Special(int key, int x, int y)
{
	double dx = 0.0;
	double dy = 0.0;
	
	// ****************************
	// Scroll left-right or up-down
	// ****************************
	
	switch(key)
	{
		case GLUT_KEY_LEFT:  dx = +1.0; break;
		case GLUT_KEY_RIGHT: dx = -1.0; break;
		case GLUT_KEY_UP:    dy = -1.0; break;
		case GLUT_KEY_DOWN:  dy = +1.0; break;
		default: return;
	}
	double ooz = SCROLL_VEL_2D/ZOOM_LIST[ZOOM_COUNT-1].z;
	ZOOM_LIST[ZOOM_COUNT-1].x += dx*ooz;
	ZOOM_LIST[ZOOM_COUNT-1].y += dy*ooz;
	Z.x += dx*ooz;
	Z.y += dy*ooz;
	
	glutPostRedisplay();
}

static int IsMouseInBox(int box[4], int x, int y)
{
	int x_in = 0;
	int y_in = 0;
	
	if(x > box[0] && x < box[2]) x_in = 1;
	if(x > box[2] && x < box[0]) x_in = 1;
	if(y > box[1] && y < box[3]) y_in = 1;
	if(y > box[3] && y < box[1]) y_in = 1;
	
	return (x_in && y_in) ? 1 : 0;
}

static int IsNonZeroBox(int box[4])
{
	if(box[0] == box[2]) return 0;
	if(box[1] == box[3]) return 0;
	return 1;
}

static void GLUT__Mouse(int button, int state, int x, int y)
{
	int isdown = (state == GLUT_DOWN) ? 1 : 0;
	
	if(button != GLUT_LEFT_BUTTON) return; // not interesting
	if(isdown == (ZOOM_STATE & 1)) return; // not interesting
	
	x -= ICON_BAR;
	
	// *****************************************************
	// Interface code is a bunch of lousy if-then statements
	// *****************************************************
	
	switch(ZOOM_STATE)
	{
		case 0:
		{
			ZOOM_HILITE = 0;
			ZOOM_STATE++;
			break;
		}
		case 1:
		{
			if(IsNonZeroBox(Z.box))
			{
				ZOOM_STATE++;
			}
			else
			{
				ZOOM_STATE--;
			}
			break;
		}
		case 2:
		{
			if(IsMouseInBox(Z.box, x, y))
			{
				ZOOM_HILITE = 1;
				ZOOM_STATE++;
			}
			else
			{
				ZOOM_HILITE = 0;
				ZOOM_STATE--;
			}
			break;
		}
		case 3:
		{
			if(IsMouseInBox(Z.box, x, y))
			{
				zoom_t zoom;
				
				Zoom(&zoom);
				ZOOM_LIST[ZOOM_COUNT] = zoom;
				ZOOM_COUNT++;
				ZOOM_STATE++;
			}
			else
			{
				ZOOM_STATE--;
			}
			break;
		}
		case 4:
		{
			ZOOM_LIST[ZOOM_COUNT-1] = Z;
			ZOOM_HILITE = 0;
			ZOOM_STATE  = 1;
			break;
		}
	}
	if(ZOOM_STATE == 1)
	{
		if(x < 0) x = 0;
		if(x > WINDOW_X-ICON_BAR) x = WINDOW_X-ICON_BAR;
		if(y < 0) y = 0;
		if(y > WINDOW_Y-STAT_BAR) y = WINDOW_Y-STAT_BAR;
		
		Z.box[0] = Z.box[2] = x;
		Z.box[1] = Z.box[3] = y;
	}
	glutPostRedisplay();
}

static void GLUT__Motion(int x, int y)
{
	x -= ICON_BAR;
	
	switch(ZOOM_STATE)
	{
		case 1:
		{
			if(x < 0) x = 0;
			if(x > WINDOW_X-ICON_BAR) x = WINDOW_X-ICON_BAR;
			if(y < 0) y = 0;
			if(y > WINDOW_Y-STAT_BAR) y = WINDOW_Y-STAT_BAR;
			
			Z.box[2] = x;
			Z.box[3] = y;
			glutPostRedisplay();
			break;
		}
		case 3:
		{
			int hilite = IsMouseInBox(Z.box, x, y);
			
			if(ZOOM_HILITE != hilite)
			{
				ZOOM_HILITE = hilite;
				glutPostRedisplay();
			}
		}
	}
}

static void LinearClamp(double &x, double dx, double target)
{
	if(x < target) {x += dx; if(x > target) x = target;} else
	if(x > target) {x -= dx; if(x < target) x = target;}
}

static void GLUT__Idle(void)
{
	if(ZOOM_STATE == 4)
	{
		// ************************************
		// Weird linear/nonlinear zoom function
		// ************************************
		
		zoom_t *target = &ZOOM_LIST[ZOOM_COUNT-1];	
		
		Z.x += (target->x - Z.x)*ZOOM_VEL_NONLINEAR;
		Z.y += (target->y - Z.y)*ZOOM_VEL_NONLINEAR;
		Z.z += (target->z - Z.z)*ZOOM_VEL_NONLINEAR;
		LinearClamp(Z.x, ZOOM_VEL_LINEAR/Z.z, target->x);
		LinearClamp(Z.y, ZOOM_VEL_LINEAR/Z.z, target->y);
		LinearClamp(Z.z, ZOOM_VEL_LINEAR*Z.z, target->z);
		
		glutPostRedisplay();
		
		if(Z.x == target->x &&
		   Z.y == target->y &&
		   Z.z == target->z )
		{
			ZOOM_STATE = 0;
		}
	}
}

int main(int argc, char *argv[])
{
	// ***************
	// Initialize GLUT
	// ***************
	
	fprintf(stdout, "Zoom!: Fractal Circle Packing Explorer\n");
	fprintf(stdout, "(c) Bernie Freidin, 1999-2000\n\n");
	fprintf(stdout, "Keys and controls:\n");
	fprintf(stdout, "  Create zoom box by left-click and dragging\n");
	fprintf(stdout, "  Zoom by left-clicking in zoom box\n");
	fprintf(stdout, "  Right-click to access menu\n");
	fprintf(stdout, "  BACKSPACE to back up to previous zoom state\n");
	fprintf(stdout, "  ESCAPE to quit\n\n");
	fprintf(stdout, "Tips:\n");
	fprintf(stdout, "  Don't switch to GRS mode (it's boring)\n");
	fprintf(stdout, "  Experiment with LOD settings!\n");

	glutInit(&argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
	glutInitWindowSize(640, 480);
	int win_id = glutCreateWindow(argv[0]);
	glutDisplayFunc(GLUT__Display);
	glutReshapeFunc(GLUT__Reshape);
	glutKeyboardFunc(GLUT__Keyboard);
	glutSpecialFunc(GLUT__Special);
	glutMouseFunc(GLUT__Mouse);
	glutMotionFunc(GLUT__Motion);
	glutIdleFunc(GLUT__Idle);
	glutSetCursor(GLUT_CURSOR_CROSSHAIR);
	
	// ****************
	// Set OpenGL state
	// ****************
	
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE);
	glHint(GL_LINE_SMOOTH_HINT, GL_NICEST);
	
	// ************************
	// Initialize MENU and PACK
	// ************************
	
	InitializeMenus();
	PACK_Initialize(&PACKINFO);
	
	// ***************
	// Initialize ZOOM
	// ***************
	
	ZOOM_LIST[0].x = 0.0;
	ZOOM_LIST[0].y = 0.0;
	ZOOM_LIST[0].z = 0.95;
	ZOOM_COUNT = 1;
	ZOOM_STATE = 0;
	Z = ZOOM_LIST[0];
	
	// ***
	// Go!
	// ***
	
	glutMainLoop();
	return 0;
}

This page © Bernie Freidin, 2000.