Using HAIR files

A HAIR file is a binary file that keeps hair model data (see HAIR file description for more information). This solution presents a way of using cyHairFile code release to easily load HAIR files and display hair models using OpenGL.

Loading HAIR files

The actual loading operation is handled using cyHairFile class by calling LoadFromFile method. The following function loads the given HAIR file into the given cyHairFile object, and calls FillDirectionArray method of cyHairFile to precompute hair directions and store them in the given directions array. These directions will be used for shading when drawing the hair model. If an error occurs, it prints proper error messages.

void LoadHairModel( const char *filename, cyHairFile &hairfile, float *&dirs )
{
	// Load the hair model
	int result = hairfile.LoadFromFile( filename );
	// Check for errors
	switch( result ) {
		case CY_HAIR_FILE_ERROR_CANT_OPEN_FILE:
			printf("Error: Cannot open hair file!\n");
			return;
		case CY_HAIR_FILE_ERROR_CANT_READ_HEADER:
			printf("Error: Cannot read hair file header!\n");
			return;
		case CY_HAIR_FILE_ERROR_WRONG_SIGNATURE:
			printf("Error: File has wrong signature!\n");
			return;
		case CY_HAIR_FILE_ERROR_READING_SEGMENTS:
			printf("Error: Cannot read hair segments!\n");
			return;
		case CY_HAIR_FILE_ERROR_READING_POINTS:
			printf("Error: Cannot read hair points!\n");
			return;
		case CY_HAIR_FILE_ERROR_READING_COLORS:
			printf("Error: Cannot read hair colors!\n");
			return;
		case CY_HAIR_FILE_ERROR_READING_THICKNESS:
			printf("Error: Cannot read hair thickness!\n");
			return;
		case CY_HAIR_FILE_ERROR_READING_TRANSPARENCY:
			printf("Error: Cannot read hair transparency!\n");
			return;
		default:
			printf("Hair file \"%s\" loaded.\n", filename);
	}
	int hairCount = hairfile.GetHeader().hair_count;
	int pointCount = hairfile.GetHeader().point_count;
	printf("Number of hair strands = %d\n", hairCount );
	printf("Number of hair points = %d\n", pointCount );
	// Compute directions
	if( hairfile.FillDirectionArray( dirs ) == 0 ) {
		printf("Error: Cannot compute hair directions!\n");
	}
}

Drawing hair models

Now that we have loaded the hair model, we are ready to draw it as line segments. The following function first initializes OpenGL arrays using the given cyHairFile object and precomputed directions, then iterates over all hair strands and draws them using glDrawArrays function.

void DrawHairModel( const cyHairFile &hairfile, float *dirs )
{
	// Set point array
	glVertexPointer( 3, GL_FLOAT, 0, hairfile.GetPointsArray() );
	glEnableClientState( GL_VERTEX_ARRAY );
	// Set normal array
	glNormalPointer( GL_FLOAT, 0, dirs );
	glEnableClientState( GL_NORMAL_ARRAY );
	// Set color array (if exists)
	float *colors = hairfile.GetColorsArray();
	if ( colors ) {
		glColorPointer( 3, GL_FLOAT, 0, colors );
		glEnableClientState( GL_COLOR_ARRAY );
	}
	// Draw arrays
	int pointIndex = 0;
	int hairCount = hairfile.GetHeader().hair_count;
	const unsigned short *segments = hairfile.GetSegmentsArray();
	if ( segments ) {
		// If segments array exists
		for ( int hairIndex=0; hairIndex < hairCount; hairIndex++ ) {
			glDrawArrays( GL_LINE_STRIP, pointIndex, segments[ hairIndex ]+1 );
			pointIndex += segments[ hairIndex ]+1;
		}
	} else {
		// If segments array does not exist, use default segment count
		int dsegs = hairfile.GetHeader().d_segments;
		for ( int hairIndex=0; hairIndex < hairCount; hairIndex++ ) {
			glDrawArrays( GL_LINE_STRIP, pointIndex, dsegs+1 );
			pointIndex += dsegs+1;
		}
	}
	// Disable arrays
	glDisableClientState( GL_VERTEX_ARRAY );
	glDisableClientState( GL_NORMAL_ARRAY );
	glDisableClientState( GL_COLOR_ARRAY );
}