//----------------------------------------------------------------------------- // Product: OpenCTM // File: compressMG2.c // Description: Implementation of the MG2 compression method. //----------------------------------------------------------------------------- // Copyright (c) 2009-2010 Marcus Geelnard // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // // 2. Altered source versions must be plainly marked as such, and must not // be misrepresented as being the original software. // // 3. This notice may not be removed or altered from any source // distribution. //----------------------------------------------------------------------------- #include #include #include "openctm.h" #include "internal.h" #ifdef __DEBUG_ #include #endif // We need PI #ifndef PI #define PI 3.141592653589793238462643f #endif //----------------------------------------------------------------------------- // _CTMgrid - 3D space subdivision grid. //----------------------------------------------------------------------------- typedef struct { // Axis-aligned boudning box for the grid. CTMfloat mMin[3]; CTMfloat mMax[3]; // How many divisions per axis (minimum 1). CTMuint mDivision[3]; // Size of each grid box. CTMfloat mSize[3]; } _CTMgrid; //----------------------------------------------------------------------------- // _CTMsortvertex - Vertex information. //----------------------------------------------------------------------------- typedef struct { // Vertex X coordinate (used for sorting). CTMfloat x; // Grid index. This is the index into the 3D space subdivision grid. CTMuint mGridIndex; // Original index (before sorting). CTMuint mOriginalIndex; } _CTMsortvertex; //----------------------------------------------------------------------------- // _ctmSetupGrid() - Setup the 3D space subdivision grid. //----------------------------------------------------------------------------- static void _ctmSetupGrid(_CTMcontext * self, _CTMgrid * aGrid) { CTMuint i; CTMfloat factor[3], sum, wantedGrids; // Calculate the mesh bounding box aGrid->mMin[0] = aGrid->mMax[0] = self->mVertices[0]; aGrid->mMin[1] = aGrid->mMax[1] = self->mVertices[1]; aGrid->mMin[2] = aGrid->mMax[2] = self->mVertices[2]; for(i = 1; i < self->mVertexCount; ++ i) { if(self->mVertices[i * 3] < aGrid->mMin[0]) aGrid->mMin[0] = self->mVertices[i * 3]; else if(self->mVertices[i * 3] > aGrid->mMax[0]) aGrid->mMax[0] = self->mVertices[i * 3]; if(self->mVertices[i * 3 + 1] < aGrid->mMin[1]) aGrid->mMin[1] = self->mVertices[i * 3 + 1]; else if(self->mVertices[i * 3 + 1] > aGrid->mMax[1]) aGrid->mMax[1] = self->mVertices[i * 3 + 1]; if(self->mVertices[i * 3 + 2] < aGrid->mMin[2]) aGrid->mMin[2] = self->mVertices[i * 3 + 2]; else if(self->mVertices[i * 3 + 2] > aGrid->mMax[2]) aGrid->mMax[2] = self->mVertices[i * 3 + 2]; } // Determine optimal grid resolution, based on the number of vertices and // the bounding box. // NOTE: This algorithm is quite crude, and could very well be optimized for // better compression levels in the future without affecting the file format // or backward compatibility at all. for(i = 0; i < 3; ++ i) factor[i] = aGrid->mMax[i] - aGrid->mMin[i]; sum = factor[0] + factor[1] + factor[2]; if(sum > 1e-30f) { sum = 1.0f / sum; for(i = 0; i < 3; ++ i) factor[i] *= sum; wantedGrids = powf(100.0f * self->mVertexCount, 1.0f / 3.0f); for(i = 0; i < 3; ++ i) { aGrid->mDivision[i] = (CTMuint) ceilf(wantedGrids * factor[i]); if(aGrid->mDivision[i] < 1) aGrid->mDivision[i] = 1; } } else { aGrid->mDivision[0] = 4; aGrid->mDivision[1] = 4; aGrid->mDivision[2] = 4; } #ifdef __DEBUG_ printf("Division: (%d %d %d)\n", aGrid->mDivision[0], aGrid->mDivision[1], aGrid->mDivision[2]); #endif // Calculate grid sizes for(i = 0; i < 3; ++ i) aGrid->mSize[i] = (aGrid->mMax[i] - aGrid->mMin[i]) / aGrid->mDivision[i]; } //----------------------------------------------------------------------------- // _ctmPointToGridIdx() - Convert a point to a grid index. //----------------------------------------------------------------------------- static CTMuint _ctmPointToGridIdx(_CTMgrid * aGrid, CTMfloat * aPoint) { CTMuint i, idx[3]; for(i = 0; i < 3; ++ i) { idx[i] = (CTMuint) floorf((aPoint[i] - aGrid->mMin[i]) / aGrid->mSize[i]); if(idx[i] >= aGrid->mDivision[i]) idx[i] = aGrid->mDivision[i] - 1; } return idx[0] + aGrid->mDivision[0] * (idx[1] + aGrid->mDivision[1] * idx[2]); } //----------------------------------------------------------------------------- // _ctmGridIdxToPoint() - Convert a grid index to a point (the min x/y/z for // the given grid box). //----------------------------------------------------------------------------- static void _ctmGridIdxToPoint(_CTMgrid * aGrid, CTMuint aIdx, CTMfloat * aPoint) { CTMuint gridIdx[3], zdiv, ydiv, i; zdiv = aGrid->mDivision[0] * aGrid->mDivision[1]; ydiv = aGrid->mDivision[0]; gridIdx[2] = aIdx / zdiv; aIdx -= gridIdx[2] * zdiv; gridIdx[1] = aIdx / ydiv; aIdx -= gridIdx[1] * ydiv; gridIdx[0] = aIdx; for(i = 0; i < 3; ++ i) aPoint[i] = gridIdx[i] * aGrid->mSize[i] + aGrid->mMin[i]; } //----------------------------------------------------------------------------- // _compareVertex() - Comparator for the vertex sorting. //----------------------------------------------------------------------------- static int _compareVertex(const void * elem1, const void * elem2) { _CTMsortvertex * v1 = (_CTMsortvertex *) elem1; _CTMsortvertex * v2 = (_CTMsortvertex *) elem2; if(v1->mGridIndex != v2->mGridIndex) return v1->mGridIndex - v2->mGridIndex; else if(v1->x < v2->x) return -1; else if(v1->x > v2->x) return 1; else return 0; } //----------------------------------------------------------------------------- // _ctmSortVertices() - Setup the vertex array. Assign each vertex to a grid // box, and sort all vertices. //----------------------------------------------------------------------------- static void _ctmSortVertices(_CTMcontext * self, _CTMsortvertex * aSortVertices, _CTMgrid * aGrid) { CTMuint i; // Prepare sort vertex array for(i = 0; i < self->mVertexCount; ++ i) { // Store vertex properties in the sort vertex array aSortVertices[i].x = self->mVertices[i * 3]; aSortVertices[i].mGridIndex = _ctmPointToGridIdx(aGrid, &self->mVertices[i * 3]); aSortVertices[i].mOriginalIndex = i; } // Sort vertices. The elements are first sorted by their grid indices, and // scondly by their x coordinates. qsort((void *) aSortVertices, self->mVertexCount, sizeof(_CTMsortvertex), _compareVertex); } //----------------------------------------------------------------------------- // _ctmReIndexIndices() - Re-index all indices, based on the sorted vertices. //----------------------------------------------------------------------------- static int _ctmReIndexIndices(_CTMcontext * self, _CTMsortvertex * aSortVertices, CTMuint * aIndices) { CTMuint i, * indexLUT; // Create temporary lookup-array, O(n) indexLUT = (CTMuint *) malloc(sizeof(CTMuint) * self->mVertexCount); if(!indexLUT) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } for(i = 0; i < self->mVertexCount; ++ i) indexLUT[aSortVertices[i].mOriginalIndex] = i; // Convert old indices to new indices, O(n) for(i = 0; i < self->mTriangleCount * 3; ++ i) aIndices[i] = indexLUT[self->mIndices[i]]; // Free temporary lookup-array free((void *) indexLUT); return CTM_TRUE; } //----------------------------------------------------------------------------- // _compareTriangle() - Comparator for the triangle sorting. //----------------------------------------------------------------------------- static int _compareTriangle(const void * elem1, const void * elem2) { CTMuint * tri1 = (CTMuint *) elem1; CTMuint * tri2 = (CTMuint *) elem2; if(tri1[0] != tri2[0]) return tri1[0] - tri2[0]; else return tri1[1] - tri2[1]; } //----------------------------------------------------------------------------- // _ctmReArrangeTriangles() - Re-arrange all triangles for optimal // compression. //----------------------------------------------------------------------------- static void _ctmReArrangeTriangles(_CTMcontext * self, CTMuint * aIndices) { CTMuint * tri, tmp, i; // Step 1: Make sure that the first index of each triangle is the smallest // one (rotate triangle nodes if necessary) for(i = 0; i < self->mTriangleCount; ++ i) { tri = &aIndices[i * 3]; if((tri[1] < tri[0]) && (tri[1] < tri[2])) { tmp = tri[0]; tri[0] = tri[1]; tri[1] = tri[2]; tri[2] = tmp; } else if((tri[2] < tri[0]) && (tri[2] < tri[1])) { tmp = tri[0]; tri[0] = tri[2]; tri[2] = tri[1]; tri[1] = tmp; } } // Step 2: Sort the triangles based on the first triangle index qsort((void *) aIndices, self->mTriangleCount, sizeof(CTMuint) * 3, _compareTriangle); } //----------------------------------------------------------------------------- // _ctmMakeIndexDeltas() - Calculate various forms of derivatives in order to // reduce data entropy. //----------------------------------------------------------------------------- static void _ctmMakeIndexDeltas(_CTMcontext * self, CTMuint * aIndices) { CTMint i; for(i = self->mTriangleCount - 1; i >= 0; -- i) { // Step 1: Calculate delta from second triangle index to the previous // second triangle index, if the previous triangle shares the same first // index, otherwise calculate the delta to the first triangle index if((i >= 1) && (aIndices[i * 3] == aIndices[(i - 1) * 3])) aIndices[i * 3 + 1] -= aIndices[(i - 1) * 3 + 1]; else aIndices[i * 3 + 1] -= aIndices[i * 3]; // Step 2: Calculate delta from third triangle index to the first triangle // index aIndices[i * 3 + 2] -= aIndices[i * 3]; // Step 3: Calculate derivative of the first triangle index if(i >= 1) aIndices[i * 3] -= aIndices[(i - 1) * 3]; } } //----------------------------------------------------------------------------- // _ctmRestoreIndices() - Restore original indices (inverse derivative // operation). //----------------------------------------------------------------------------- static void _ctmRestoreIndices(_CTMcontext * self, CTMuint * aIndices) { CTMuint i; for(i = 0; i < self->mTriangleCount; ++ i) { // Step 1: Reverse derivative of the first triangle index if(i >= 1) aIndices[i * 3] += aIndices[(i - 1) * 3]; // Step 2: Reverse delta from third triangle index to the first triangle // index aIndices[i * 3 + 2] += aIndices[i * 3]; // Step 3: Reverse delta from second triangle index to the previous // second triangle index, if the previous triangle shares the same first // index, otherwise reverse the delta to the first triangle index if((i >= 1) && (aIndices[i * 3] == aIndices[(i - 1) * 3])) aIndices[i * 3 + 1] += aIndices[(i - 1) * 3 + 1]; else aIndices[i * 3 + 1] += aIndices[i * 3]; } } //----------------------------------------------------------------------------- // _ctmMakeVertexDeltas() - Calculate various forms of derivatives in order to // reduce data entropy. //----------------------------------------------------------------------------- static void _ctmMakeVertexDeltas(_CTMcontext * self, CTMint * aIntVertices, _CTMsortvertex * aSortVertices, _CTMgrid * aGrid) { CTMuint i, gridIdx, prevGridIndex, oldIdx; CTMfloat gridOrigin[3], scale; CTMint deltaX, prevDeltaX; // Vertex scaling factor scale = 1.0f / self->mVertexPrecision; prevGridIndex = 0x7fffffff; prevDeltaX = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Get grid box origin gridIdx = aSortVertices[i].mGridIndex; _ctmGridIdxToPoint(aGrid, gridIdx, gridOrigin); // Get old vertex coordinate index (before vertex sorting) oldIdx = aSortVertices[i].mOriginalIndex; // Store delta to the grid box origin in the integer vertex array. For the // X axis (which is sorted) we also do the delta to the previous coordinate // in the box. deltaX = (CTMint) floorf(scale * (self->mVertices[oldIdx * 3] - gridOrigin[0]) + 0.5f); if(gridIdx == prevGridIndex) aIntVertices[i * 3] = deltaX - prevDeltaX; else aIntVertices[i * 3] = deltaX; aIntVertices[i * 3 + 1] = (CTMint) floorf(scale * (self->mVertices[oldIdx * 3 + 1] - gridOrigin[1]) + 0.5f); aIntVertices[i * 3 + 2] = (CTMint) floorf(scale * (self->mVertices[oldIdx * 3 + 2] - gridOrigin[2]) + 0.5f); prevGridIndex = gridIdx; prevDeltaX = deltaX; } } //----------------------------------------------------------------------------- // _ctmRestoreVertices() - Calculate inverse derivatives of the vertices. //----------------------------------------------------------------------------- static void _ctmRestoreVertices(_CTMcontext * self, CTMint * aIntVertices, CTMuint * aGridIndices, _CTMgrid * aGrid, CTMfloat * aVertices) { CTMuint i, gridIdx, prevGridIndex; CTMfloat gridOrigin[3], scale; CTMint deltaX, prevDeltaX; scale = self->mVertexPrecision; prevGridIndex = 0x7fffffff; prevDeltaX = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Get grid box origin gridIdx = aGridIndices[i]; _ctmGridIdxToPoint(aGrid, gridIdx, gridOrigin); // Restore original point deltaX = aIntVertices[i * 3]; if(gridIdx == prevGridIndex) deltaX += prevDeltaX; aVertices[i * 3] = scale * deltaX + gridOrigin[0]; aVertices[i * 3 + 1] = scale * aIntVertices[i * 3 + 1] + gridOrigin[1]; aVertices[i * 3 + 2] = scale * aIntVertices[i * 3 + 2] + gridOrigin[2]; prevGridIndex = gridIdx; prevDeltaX = deltaX; } } //----------------------------------------------------------------------------- // _ctmCalcSmoothNormals() - Calculate the smooth normals for a given mesh. // These are used as the nominal normals for normal deltas & reconstruction. //----------------------------------------------------------------------------- static void _ctmCalcSmoothNormals(_CTMcontext * self, CTMfloat * aVertices, CTMuint * aIndices, CTMfloat * aSmoothNormals) { CTMuint i, j, k, tri[3]; CTMfloat len; CTMfloat v1[3], v2[3], n[3]; // Clear smooth normals array for(i = 0; i < 3 * self->mVertexCount; ++ i) aSmoothNormals[i] = 0.0f; // Calculate sums of all neigbouring triangle normals for each vertex for(i = 0; i < self->mTriangleCount; ++ i) { // Get triangle corner indices for(j = 0; j < 3; ++ j) tri[j] = aIndices[i * 3 + j]; // Calculate the normalized cross product of two triangle edges (i.e. the // flat triangle normal) for(j = 0; j < 3; ++ j) { v1[j] = aVertices[tri[1] * 3 + j] - aVertices[tri[0] * 3 + j]; v2[j] = aVertices[tri[2] * 3 + j] - aVertices[tri[0] * 3 + j]; } n[0] = v1[1] * v2[2] - v1[2] * v2[1]; n[1] = v1[2] * v2[0] - v1[0] * v2[2]; n[2] = v1[0] * v2[1] - v1[1] * v2[0]; len = sqrtf(n[0] * n[0] + n[1] * n[1] + n[2] * n[2]); if(len > 1e-10f) len = 1.0f / len; else len = 1.0f; for(j = 0; j < 3; ++ j) n[j] *= len; // Add the flat normal to all three triangle vertices for(k = 0; k < 3; ++ k) for(j = 0; j < 3; ++ j) aSmoothNormals[tri[k] * 3 + j] += n[j]; } // Normalize the normal sums, which gives the unit length smooth normals for(i = 0; i < self->mVertexCount; ++ i) { len = sqrtf(aSmoothNormals[i * 3] * aSmoothNormals[i * 3] + aSmoothNormals[i * 3 + 1] * aSmoothNormals[i * 3 + 1] + aSmoothNormals[i * 3 + 2] * aSmoothNormals[i * 3 + 2]); if(len > 1e-10f) len = 1.0f / len; else len = 1.0f; for(j = 0; j < 3; ++ j) aSmoothNormals[i * 3 + j] *= len; } } //----------------------------------------------------------------------------- // _ctmMakeNormalCoordSys() - Create an ortho-normalized coordinate system // where the Z-axis is aligned with the given normal. // Note 1: This function is central to how the compressed normal data is // interpreted, and it can not be changed (mathematically) without making the // coder/decoder incompatible with other versions of the library! // Note 2: Since we do this for every single normal, this routine needs to be // fast. The current implementation uses: 12 MUL, 1 DIV, 1 SQRT, ~6 ADD. //----------------------------------------------------------------------------- static void _ctmMakeNormalCoordSys(CTMfloat * aNormal, CTMfloat * aBasisAxes) { CTMfloat len, * x, * y, * z; CTMuint i; // Pointers to the basis axes (aBasisAxes is a 3x3 matrix) x = aBasisAxes; y = &aBasisAxes[3]; z = &aBasisAxes[6]; // Z = normal (must be unit length!) for(i = 0; i < 3; ++ i) z[i] = aNormal[i]; // Calculate a vector that is guaranteed to be orthogonal to the normal, non- // zero, and a continuous function of the normal (no discrete jumps): // X = (0,0,1) x normal + (1,0,0) x normal x[0] = -aNormal[1]; x[1] = aNormal[0] - aNormal[2]; x[2] = aNormal[1]; // Normalize the new X axis (note: |x[2]| = |x[0]|) len = sqrtf(2.0 * x[0] * x[0] + x[1] * x[1]); if(len > 1.0e-20f) { len = 1.0f / len; x[0] *= len; x[1] *= len; x[2] *= len; } // Let Y = Z x X (no normalization needed, since |Z| = |X| = 1) y[0] = z[1] * x[2] - z[2] * x[1]; y[1] = z[2] * x[0] - z[0] * x[2]; y[2] = z[0] * x[1] - z[1] * x[0]; } //----------------------------------------------------------------------------- // _ctmMakeNormalDeltas() - Convert the normals to a new coordinate system: // magnitude, phi, theta (relative to predicted smooth normals). //----------------------------------------------------------------------------- static CTMint _ctmMakeNormalDeltas(_CTMcontext * self, CTMint * aIntNormals, CTMfloat * aVertices, CTMuint * aIndices, _CTMsortvertex * aSortVertices) { CTMuint i, j, oldIdx, intPhi; CTMfloat magn, phi, theta, scale, thetaScale; CTMfloat * smoothNormals, n[3], n2[3], basisAxes[9]; // Allocate temporary memory for the nominal vertex normals smoothNormals = (CTMfloat *) malloc(3 * sizeof(CTMfloat) * self->mVertexCount); if(!smoothNormals) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } // Calculate smooth normals (Note: aVertices and aIndices use the sorted // index space, so smoothNormals will too) _ctmCalcSmoothNormals(self, aVertices, aIndices, smoothNormals); // Normal scaling factor scale = 1.0f / self->mNormalPrecision; for(i = 0; i < self->mVertexCount; ++ i) { // Get old normal index (before vertex sorting) oldIdx = aSortVertices[i].mOriginalIndex; // Calculate normal magnitude (should always be 1.0 for unit length normals) magn = sqrtf(self->mNormals[oldIdx * 3] * self->mNormals[oldIdx * 3] + self->mNormals[oldIdx * 3 + 1] * self->mNormals[oldIdx * 3 + 1] + self->mNormals[oldIdx * 3 + 2] * self->mNormals[oldIdx * 3 + 2]); if(magn < 1e-10f) magn = 1.0f; // Invert magnitude if the normal is negative compared to the predicted // smooth normal if((smoothNormals[i * 3] * self->mNormals[oldIdx * 3] + smoothNormals[i * 3 + 1] * self->mNormals[oldIdx * 3 + 1] + smoothNormals[i * 3 + 2] * self->mNormals[oldIdx * 3 + 2]) < 0.0f) magn = -magn; // Store the magnitude in the first element of the three normal elements aIntNormals[i * 3] = (CTMint) floorf(scale * magn + 0.5f); // Normalize the normal (1 / magn) - and flip it if magn < 0 magn = 1.0f / magn; for(j = 0; j < 3; ++ j) n[j] = self->mNormals[oldIdx * 3 + j] * magn; // Convert the normal to angular representation (phi, theta) in a coordinate // system where the nominal (smooth) normal is the Z-axis _ctmMakeNormalCoordSys(&smoothNormals[i * 3], basisAxes); for(j = 0; j < 3; ++ j) n2[j] = basisAxes[j * 3] * n[0] + basisAxes[j * 3 + 1] * n[1] + basisAxes[j * 3 + 2] * n[2]; if(n2[2] >= 1.0f) phi = 0.0f; else phi = acosf(n2[2]); theta = atan2f(n2[1], n2[0]); // Round phi and theta (spherical coordinates) to integers. Note: We let the // theta resolution vary with the x/y circumference (roughly phi). intPhi = (CTMint) floorf(phi * (scale / (0.5f * PI)) + 0.5f); if(intPhi == 0) thetaScale = 0.0f; else if(intPhi <= 4) thetaScale = 2.0f / PI; else thetaScale = ((CTMfloat) intPhi) / (2.0f * PI); aIntNormals[i * 3 + 1] = intPhi; aIntNormals[i * 3 + 2] = (CTMint) floorf((theta + PI) * thetaScale + 0.5f); } // Free temporary resources free(smoothNormals); return CTM_TRUE; } //----------------------------------------------------------------------------- // _ctmRestoreNormals() - Convert the normals back to cartesian coordinates. //----------------------------------------------------------------------------- static CTMint _ctmRestoreNormals(_CTMcontext * self, CTMint * aIntNormals) { CTMuint i, j, intPhi; CTMfloat magn, phi, theta, scale, thetaScale; CTMfloat * smoothNormals, n[3], n2[3], basisAxes[9]; // Allocate temporary memory for the nominal vertex normals smoothNormals = (CTMfloat *) malloc(3 * sizeof(CTMfloat) * self->mVertexCount); if(!smoothNormals) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } // Calculate smooth normals (nominal normals) _ctmCalcSmoothNormals(self, self->mVertices, self->mIndices, smoothNormals); // Normal scaling factor scale = self->mNormalPrecision; for(i = 0; i < self->mVertexCount; ++ i) { // Get the normal magnitude from the first of the three normal elements magn = aIntNormals[i * 3] * scale; // Get phi and theta (spherical coordinates, relative to the smooth normal). intPhi = aIntNormals[i * 3 + 1]; phi = intPhi * (0.5f * PI) * scale; if(intPhi == 0) thetaScale = 0.0f; else if(intPhi <= 4) thetaScale = PI / 2.0f; else thetaScale = (2.0f * PI) / ((CTMfloat) intPhi); theta = aIntNormals[i * 3 + 2] * thetaScale - PI; // Convert the normal from the angular representation (phi, theta) back to // cartesian coordinates n2[0] = sinf(phi) * cosf(theta); n2[1] = sinf(phi) * sinf(theta); n2[2] = cosf(phi); _ctmMakeNormalCoordSys(&smoothNormals[i * 3], basisAxes); for(j = 0; j < 3; ++ j) n[j] = basisAxes[j] * n2[0] + basisAxes[3 + j] * n2[1] + basisAxes[6 + j] * n2[2]; // Apply normal magnitude, and output to the normals array for(j = 0; j < 3; ++ j) self->mNormals[i * 3 + j] = n[j] * magn; } // Free temporary resources free(smoothNormals); return CTM_TRUE; } //----------------------------------------------------------------------------- // _ctmMakeUVCoordDeltas() - Calculate various forms of derivatives in order // to reduce data entropy. //----------------------------------------------------------------------------- static void _ctmMakeUVCoordDeltas(_CTMcontext * self, _CTMfloatmap * aMap, CTMint * aIntUVCoords, _CTMsortvertex * aSortVertices) { CTMuint i, oldIdx; CTMint u, v, prevU, prevV; CTMfloat scale; // UV coordinate scaling factor scale = 1.0f / aMap->mPrecision; prevU = prevV = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Get old UV coordinate index (before vertex sorting) oldIdx = aSortVertices[i].mOriginalIndex; // Convert to fixed point u = (CTMint) floorf(scale * aMap->mValues[oldIdx * 2] + 0.5f); v = (CTMint) floorf(scale * aMap->mValues[oldIdx * 2 + 1] + 0.5f); // Calculate delta and store it in the converted array. NOTE: Here we rely // on the fact that vertices are sorted, and usually close to each other, // which means that UV coordinates should also be close to each other... aIntUVCoords[i * 2] = u - prevU; aIntUVCoords[i * 2 + 1] = v - prevV; prevU = u; prevV = v; } } //----------------------------------------------------------------------------- // _ctmRestoreUVCoords() - Calculate inverse derivatives of the UV // coordinates. //----------------------------------------------------------------------------- static void _ctmRestoreUVCoords(_CTMcontext * self, _CTMfloatmap * aMap, CTMint * aIntUVCoords) { CTMuint i; CTMint u, v, prevU, prevV; CTMfloat scale; // UV coordinate scaling factor scale = aMap->mPrecision; prevU = prevV = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Calculate inverse delta u = aIntUVCoords[i * 2] + prevU; v = aIntUVCoords[i * 2 + 1] + prevV; // Convert to floating point aMap->mValues[i * 2] = (CTMfloat) u * scale; aMap->mValues[i * 2 + 1] = (CTMfloat) v * scale; prevU = u; prevV = v; } } //----------------------------------------------------------------------------- // _ctmMakeAttribDeltas() - Calculate various forms of derivatives in order // to reduce data entropy. //----------------------------------------------------------------------------- static void _ctmMakeAttribDeltas(_CTMcontext * self, _CTMfloatmap * aMap, CTMint * aIntAttribs, _CTMsortvertex * aSortVertices) { CTMuint i, j, oldIdx; CTMint value[4], prev[4]; CTMfloat scale; // Attribute scaling factor scale = 1.0f / aMap->mPrecision; for(j = 0; j < 4; ++ j) prev[j] = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Get old attribute index (before vertex sorting) oldIdx = aSortVertices[i].mOriginalIndex; // Convert to fixed point, and calculate delta and store it in the converted // array. NOTE: Here we rely on the fact that vertices are sorted, and // usually close to each other, which means that attributes should also // be close to each other (and we assume that they somehow vary slowly with // the geometry)... for(j = 0; j < 4; ++ j) { value[j] = (CTMint) floorf(scale * aMap->mValues[oldIdx * 4 + j] + 0.5f); aIntAttribs[i * 4 + j] = value[j] - prev[j]; prev[j] = value[j]; } } } //----------------------------------------------------------------------------- // _ctmRestoreAttribs() - Calculate inverse derivatives of the vertex // attributes. //----------------------------------------------------------------------------- static void _ctmRestoreAttribs(_CTMcontext * self, _CTMfloatmap * aMap, CTMint * aIntAttribs) { CTMuint i, j; CTMint value[4], prev[4]; CTMfloat scale; // Attribute scaling factor scale = aMap->mPrecision; for(j = 0; j < 4; ++ j) prev[j] = 0; for(i = 0; i < self->mVertexCount; ++ i) { // Calculate inverse delta, and convert to floating point for(j = 0; j < 4; ++ j) { value[j] = aIntAttribs[i * 4 + j] + prev[j]; aMap->mValues[i * 4 + j] = (CTMfloat) value[j] * scale; prev[j] = value[j]; } } } //----------------------------------------------------------------------------- // _ctmCompressMesh_MG2() - Compress the mesh that is stored in the CTM // context, and write it the the output stream in the CTM context. //----------------------------------------------------------------------------- int _ctmCompressMesh_MG2(_CTMcontext * self) { _CTMgrid grid; _CTMsortvertex * sortVertices; _CTMfloatmap * map; CTMuint * indices, * deltaIndices, * gridIndices; CTMint * intVertices, * intNormals, * intUVCoords, * intAttribs; CTMfloat * restoredVertices; CTMuint i; #ifdef __DEBUG_ printf("COMPRESSION METHOD: MG2\n"); #endif // Setup 3D space subdivision grid _ctmSetupGrid(self, &grid); // Write MG2-specific header information to the stream _ctmStreamWrite(self, (void *) "MG2H", 4); _ctmStreamWriteFLOAT(self, self->mVertexPrecision); _ctmStreamWriteFLOAT(self, self->mNormalPrecision); _ctmStreamWriteFLOAT(self, grid.mMin[0]); _ctmStreamWriteFLOAT(self, grid.mMin[1]); _ctmStreamWriteFLOAT(self, grid.mMin[2]); _ctmStreamWriteFLOAT(self, grid.mMax[0]); _ctmStreamWriteFLOAT(self, grid.mMax[1]); _ctmStreamWriteFLOAT(self, grid.mMax[2]); _ctmStreamWriteUINT(self, grid.mDivision[0]); _ctmStreamWriteUINT(self, grid.mDivision[1]); _ctmStreamWriteUINT(self, grid.mDivision[2]); // Prepare (sort) vertices sortVertices = (_CTMsortvertex *) malloc(sizeof(_CTMsortvertex) * self->mVertexCount); if(!sortVertices) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } _ctmSortVertices(self, sortVertices, &grid); // Convert vertices to integers and calculate vertex deltas (entropy-reduction) intVertices = (CTMint *) malloc(sizeof(CTMint) * 3 * self->mVertexCount); if(!intVertices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) sortVertices); return CTM_FALSE; } _ctmMakeVertexDeltas(self, intVertices, sortVertices, &grid); // Write vertices #ifdef __DEBUG_ printf("Vertices: "); #endif _ctmStreamWrite(self, (void *) "VERT", 4); if(!_ctmStreamWritePackedInts(self, intVertices, self->mVertexCount, 3, CTM_FALSE)) { free((void *) intVertices); free((void *) sortVertices); return CTM_FALSE; } // Prepare grid indices (deltas) gridIndices = (CTMuint *) malloc(sizeof(CTMuint) * self->mVertexCount); if(!gridIndices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) intVertices); free((void *) sortVertices); return CTM_FALSE; } gridIndices[0] = sortVertices[0].mGridIndex; for(i = 1; i < self->mVertexCount; ++ i) gridIndices[i] = sortVertices[i].mGridIndex - sortVertices[i - 1].mGridIndex; // Write grid indices #ifdef __DEBUG_ printf("Grid indices: "); #endif _ctmStreamWrite(self, (void *) "GIDX", 4); if(!_ctmStreamWritePackedInts(self, (CTMint *) gridIndices, self->mVertexCount, 1, CTM_FALSE)) { free((void *) gridIndices); free((void *) intVertices); free((void *) sortVertices); return CTM_FALSE; } // Calculate the result of the compressed -> decompressed vertices, in order // to use the same vertex data for calculating nominal normals as the // decompression routine (i.e. compensate for the vertex error when // calculating the normals) restoredVertices = (CTMfloat *) malloc(sizeof(CTMfloat) * 3 * self->mVertexCount); if(!restoredVertices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) gridIndices); free((void *) intVertices); free((void *) sortVertices); return CTM_FALSE; } for(i = 1; i < self->mVertexCount; ++ i) gridIndices[i] += gridIndices[i - 1]; _ctmRestoreVertices(self, intVertices, gridIndices, &grid, restoredVertices); // Free temporary resources free((void *) gridIndices); free((void *) intVertices); // Perpare (sort) indices indices = (CTMuint *) malloc(sizeof(CTMuint) * self->mTriangleCount * 3); if(!indices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } if(!_ctmReIndexIndices(self, sortVertices, indices)) { free((void *) indices); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } _ctmReArrangeTriangles(self, indices); // Calculate index deltas (entropy-reduction) deltaIndices = (CTMuint *) malloc(sizeof(CTMuint) * self->mTriangleCount * 3); if(!indices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) indices); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } for(i = 0; i < self->mTriangleCount * 3; ++ i) deltaIndices[i] = indices[i]; _ctmMakeIndexDeltas(self, deltaIndices); // Write triangle indices #ifdef __DEBUG_ printf("Indices: "); #endif _ctmStreamWrite(self, (void *) "INDX", 4); if(!_ctmStreamWritePackedInts(self, (CTMint *) deltaIndices, self->mTriangleCount, 3, CTM_FALSE)) { free((void *) deltaIndices); free((void *) indices); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } // Free temporary data for the indices free((void *) deltaIndices); if(self->mNormals) { // Convert normals to integers and calculate deltas (entropy-reduction) intNormals = (CTMint *) malloc(sizeof(CTMint) * 3 * self->mVertexCount); if(!intNormals) { self->mError = CTM_OUT_OF_MEMORY; free((void *) indices); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } if(!_ctmMakeNormalDeltas(self, intNormals, restoredVertices, indices, sortVertices)) { free((void *) indices); free((void *) intNormals); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } // Write normals #ifdef __DEBUG_ printf("Normals: "); #endif _ctmStreamWrite(self, (void *) "NORM", 4); if(!_ctmStreamWritePackedInts(self, intNormals, self->mVertexCount, 3, CTM_FALSE)) { free((void *) indices); free((void *) intNormals); free((void *) restoredVertices); free((void *) sortVertices); return CTM_FALSE; } // Free temporary normal data free((void *) intNormals); } // Free restored indices and vertices free((void *) indices); free((void *) restoredVertices); // Write UV maps map = self->mUVMaps; while(map) { // Convert UV coordinates to integers and calculate deltas (entropy-reduction) intUVCoords = (CTMint *) malloc(sizeof(CTMint) * 2 * self->mVertexCount); if(!intUVCoords) { self->mError = CTM_OUT_OF_MEMORY; free((void *) sortVertices); return CTM_FALSE; } _ctmMakeUVCoordDeltas(self, map, intUVCoords, sortVertices); // Write UV coordinates #ifdef __DEBUG_ printf("Texture coordinates (%s): ", map->mName ? map->mName : "no name"); #endif _ctmStreamWrite(self, (void *) "TEXC", 4); _ctmStreamWriteSTRING(self, map->mName); _ctmStreamWriteSTRING(self, map->mFileName); _ctmStreamWriteFLOAT(self, map->mPrecision); if(!_ctmStreamWritePackedInts(self, intUVCoords, self->mVertexCount, 2, CTM_TRUE)) { free((void *) intUVCoords); free((void *) sortVertices); return CTM_FALSE; } // Free temporary UV coordinate data free((void *) intUVCoords); map = map->mNext; } // Write vertex attribute maps map = self->mAttribMaps; while(map) { // Convert vertex attributes to integers and calculate deltas (entropy-reduction) intAttribs = (CTMint *) malloc(sizeof(CTMint) * 4 * self->mVertexCount); if(!intAttribs) { self->mError = CTM_OUT_OF_MEMORY; free((void *) sortVertices); return CTM_FALSE; } _ctmMakeAttribDeltas(self, map, intAttribs, sortVertices); // Write vertex attributes #ifdef __DEBUG_ printf("Vertex attributes (%s): ", map->mName ? map->mName : "no name"); #endif _ctmStreamWrite(self, (void *) "ATTR", 4); _ctmStreamWriteSTRING(self, map->mName); _ctmStreamWriteFLOAT(self, map->mPrecision); if(!_ctmStreamWritePackedInts(self, intAttribs, self->mVertexCount, 4, CTM_TRUE)) { free((void *) intAttribs); free((void *) sortVertices); return CTM_FALSE; } // Free temporary vertex attribute data free((void *) intAttribs); map = map->mNext; } // Free temporary data free((void *) sortVertices); return CTM_TRUE; } //----------------------------------------------------------------------------- // _ctmUncompressMesh_MG2() - Uncmpress the mesh from the input stream in the // CTM context, and store the resulting mesh in the CTM context. //----------------------------------------------------------------------------- int _ctmUncompressMesh_MG2(_CTMcontext * self) { CTMuint * gridIndices, i; CTMint * intVertices, * intNormals, * intUVCoords, * intAttribs; _CTMfloatmap * map; _CTMgrid grid; // Read MG2-specific header information from the stream if(_ctmStreamReadUINT(self) != FOURCC("MG2H")) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } self->mVertexPrecision = _ctmStreamReadFLOAT(self); if(self->mVertexPrecision <= 0.0f) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } self->mNormalPrecision = _ctmStreamReadFLOAT(self); if(self->mNormalPrecision <= 0.0f) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } grid.mMin[0] = _ctmStreamReadFLOAT(self); grid.mMin[1] = _ctmStreamReadFLOAT(self); grid.mMin[2] = _ctmStreamReadFLOAT(self); grid.mMax[0] = _ctmStreamReadFLOAT(self); grid.mMax[1] = _ctmStreamReadFLOAT(self); grid.mMax[2] = _ctmStreamReadFLOAT(self); if((grid.mMax[0] < grid.mMin[0]) || (grid.mMax[1] < grid.mMin[1]) || (grid.mMax[2] < grid.mMin[2])) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } grid.mDivision[0] = _ctmStreamReadUINT(self); grid.mDivision[1] = _ctmStreamReadUINT(self); grid.mDivision[2] = _ctmStreamReadUINT(self); if((grid.mDivision[0] < 1) || (grid.mDivision[1] < 1) || (grid.mDivision[2] < 1)) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } // Initialize 3D space subdivision grid for(i = 0; i < 3; ++ i) grid.mSize[i] = (grid.mMax[i] - grid.mMin[i]) / grid.mDivision[i]; // Read vertices if(_ctmStreamReadUINT(self) != FOURCC("VERT")) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } intVertices = (CTMint *) malloc(sizeof(CTMint) * self->mVertexCount * 3); if(!intVertices) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, intVertices, self->mVertexCount, 3, CTM_FALSE)) { free((void *) intVertices); return CTM_FALSE; } // Read grid indices if(_ctmStreamReadUINT(self) != FOURCC("GIDX")) { free((void *) intVertices); self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } gridIndices = (CTMuint *) malloc(sizeof(CTMuint) * self->mVertexCount); if(!gridIndices) { self->mError = CTM_OUT_OF_MEMORY; free((void *) intVertices); return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, (CTMint *) gridIndices, self->mVertexCount, 1, CTM_FALSE)) { free((void *) gridIndices); free((void *) intVertices); return CTM_FALSE; } // Restore grid indices (deltas) for(i = 1; i < self->mVertexCount; ++ i) gridIndices[i] += gridIndices[i - 1]; // Restore vertices _ctmRestoreVertices(self, intVertices, gridIndices, &grid, self->mVertices); // Free temporary resources free((void *) gridIndices); free((void *) intVertices); // Read triangle indices if(_ctmStreamReadUINT(self) != FOURCC("INDX")) { self->mError = CTM_BAD_FORMAT; return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, (CTMint *) self->mIndices, self->mTriangleCount, 3, CTM_FALSE)) return CTM_FALSE; // Restore indices _ctmRestoreIndices(self, self->mIndices); // Check that all indices are within range for(i = 0; i < (self->mTriangleCount * 3); ++ i) { if(self->mIndices[i] >= self->mVertexCount) { self->mError = CTM_INVALID_MESH; return CTM_FALSE; } } // Read normals if(self->mNormals) { intNormals = (CTMint *) malloc(sizeof(CTMint) * self->mVertexCount * 3); if(!intNormals) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } if(_ctmStreamReadUINT(self) != FOURCC("NORM")) { self->mError = CTM_BAD_FORMAT; free((void *) intNormals); return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, intNormals, self->mVertexCount, 3, CTM_FALSE)) { free((void *) intNormals); return CTM_FALSE; } // Restore normals if(!_ctmRestoreNormals(self, intNormals)) { free((void *) intNormals); return CTM_FALSE; } // Free temporary normals data free((void *) intNormals); } // Read UV maps map = self->mUVMaps; while(map) { intUVCoords = (CTMint *) malloc(sizeof(CTMint) * self->mVertexCount * 2); if(!intUVCoords) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } if(_ctmStreamReadUINT(self) != FOURCC("TEXC")) { self->mError = CTM_BAD_FORMAT; free((void *) intUVCoords); return CTM_FALSE; } _ctmStreamReadSTRING(self, &map->mName); _ctmStreamReadSTRING(self, &map->mFileName); map->mPrecision = _ctmStreamReadFLOAT(self); if(map->mPrecision <= 0.0f) { self->mError = CTM_BAD_FORMAT; free((void *) intUVCoords); return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, intUVCoords, self->mVertexCount, 2, CTM_TRUE)) { free((void *) intUVCoords); return CTM_FALSE; } // Restore UV coordinates _ctmRestoreUVCoords(self, map, intUVCoords); // Free temporary UV coordinate data free((void *) intUVCoords); map = map->mNext; } // Read vertex attribute maps map = self->mAttribMaps; while(map) { intAttribs = (CTMint *) malloc(sizeof(CTMint) * self->mVertexCount * 4); if(!intAttribs) { self->mError = CTM_OUT_OF_MEMORY; return CTM_FALSE; } if(_ctmStreamReadUINT(self) != FOURCC("ATTR")) { self->mError = CTM_BAD_FORMAT; free((void *) intAttribs); return CTM_FALSE; } _ctmStreamReadSTRING(self, &map->mName); map->mPrecision = _ctmStreamReadFLOAT(self); if(map->mPrecision <= 0.0f) { self->mError = CTM_BAD_FORMAT; free((void *) intAttribs); return CTM_FALSE; } if(!_ctmStreamReadPackedInts(self, intAttribs, self->mVertexCount, 4, CTM_TRUE)) { free((void *) intAttribs); return CTM_FALSE; } // Restore vertex attributes _ctmRestoreAttribs(self, map, intAttribs); // Free temporary vertex attribute data free((void *) intAttribs); map = map->mNext; } return CTM_TRUE; }