Using Binary Curve Collection (BCC) files

BCC stands for "Binary Curve Collection." I use the BCC file format for yarn-level cloth models. You can find the detailed description of the BCC file format on the Yarn-level Cloth Models page on my website. This solution presents a way of easily loading these files.

Loading the BCC file header

For easily loading the BCC files, I will use the following struct.

struct BCCHeader
{
	char sign[3];
	unsigned char byteCount;
	char curveType[2];
	char dimensions;
	char upDimension;
	uint64_t curveCount;
	uint64_t totalControlPointCount;
	char fileInfo[40];
};

Using this struct loading the header is quite simple.

BCCHeader header;
FILE *pFile = fopen("filename.bcc", "rb");
fread(&header, sizeof(header), 1, pFile);

Then, we should check if this was a valid BCC file.

if ( header.sign[0] != 'B' ) return -1; // Invalid file signature
if ( header.sign[1] != 'C' ) return -1; // Invalid file signature
if ( header.sign[2] != 'C' ) return -1; // Invalid file signature
if ( header.byteCount != 0x44 ) return -1; // Only supporting 4-byte integers and floats

You can also check the curve type to make sure that it is what you expect.

if ( header.curveType[0] != 'C' ) return -1; // Not a Catmull-Rom curve
if ( header.curveType[1] != '0' ) return -1; // Not uniform parameterization
if ( header.dimensions != 3 ) return -1; // Only curves in 3D

You can find more information on Camtull-Rom curves and their parameterization here.

Loading the BCC file data block

The data block consists of entrees for each curve. The entrees begin with an integer that indicates the number of control points for the curve, followed by the control points of the curve.

It might be a good idea to allocate space for all control points at once (the number is provided in the header). Then, we must also keep the index of the first control point per curve.

IMPORTANT: The number of control points for a curve CAN be negative. This means that the curve represents a closed loop.

std::vector<cy::Vec3f> controlPoints( header.totalControlPointCount );
std::vector<int> firstControlPoint( header.curveCount + 1 );
std::vector<char> isCurveLoop( header.curveCount );
float *cp = controlPoints.data();
int prevCP = 0;
for ( uint64_t i=0; i<header.curveCount; i++ ) {
	int curveControlPointCount;
	fread(&curveControlPointCount, sizeof(int), 1, pFile);
	isCurveLoop[i] = curveControlPointCount < 0;
	if ( curveControlPointCount < 0 ) curveControlPointCount = -curveControlPointCount;
	fread(cp, sizeof(float), curveControlPointCount, pFile);
	cp += curveControlPointCount;
	firstControlPoint[i] = prevCP;
	prevCP += curveControlPointCount;
}
firstControlPoint[header.curveCount] = prevCP;

Drawing the curves

Here I provide a simple (incomplete) code for drawing these curves as line segments (not as Catmull-Rom curves, as they should be drawn). Also note that the following code uses the old OpenGL standard (GL version 3.2 or earlier).

for ( uint64_t i=0; i<header.curveCount; i++ ) {
	glDrawArrays( 
		isCurveLoop[i] ? GL_LINE_LOOP : GL_LINE_STRIP,
		controlPoints.data() + fistControlPoint[i],
		firstControlPoint[i+1] - firstControlPoint[i] );
}