/**************************************************************************\ * * Copyright (c) 1999 Microsoft Corporation * * Module Name: * * mesh.cxx * * Abstract: * * Implementation of spline meshes * * Revision History: * * 01/18/1999 davidx * Created it. * \**************************************************************************/ #include "precomp.hxx" // // Mesh constructor // Mesh::Mesh( INT gridRows, INT gridCols ) { this->gridRows = gridRows; this->gridCols = gridCols; ptTemp = NULL; ptxTemp1 = ptxTemp2 = NULL; dstSize.cx = dstSize.cy = 2; mesh = new PointX[gridRows*gridCols]; if (mesh == NULL) Error("Couldn't allocate memory to hold mesh grids\n"); initMesh(); } // // Mesh destructor // Mesh::~Mesh() { delete[] mesh; delete[] ptTemp; delete[] ptxTemp1; delete[] ptxTemp2; } // // Initialize mesh configuration // VOID Mesh::initMesh() { PointX* p = mesh; double sx = 1.0 / (gridCols-1); double sy = 1.0 / (gridRows-1); for (INT row=0; row < gridRows; row++) { double y = row * sy; for (INT col=0; col < gridCols; col++) { p->x = col * sx; p->y = y; p++; } } } // // Allocate temporary memory to hold // VOID Mesh::allocTempPoints() { // Check to see if we have already allocated // temporary memory for holding spline and bezier points if (ptTemp) return; INT count = max(gridRows, gridCols); ptxTemp1 = new PointX[count]; ptxTemp2 = new PointX[3*count+1]; ptTemp = new POINT[3*count+1]; if (!ptxTemp1 || !ptxTemp2 || !ptTemp) Error("Out of memory\n"); } // // Convert a spline curve to Bezier segments // VOID Mesh::spline2Bezier( const PointX* srcPts, PointX* dstPts, INT count ) { const PointX* p; PointX tempPts[4]; // We use the default tension of 0.5 double a3 = 0.5 / 3; *dstPts = *srcPts; for (INT i=0; i < count; i++) { if (i > 1 && i < count-1) p = srcPts + (i-1); else { tempPts[0] = srcPts[(i > 0) ? (i-1) : 0]; tempPts[1] = srcPts[i]; tempPts[2] = srcPts[(i+1 < count) ? (i+1) : count]; tempPts[3] = srcPts[(i+2 < count) ? (i+2) : count]; p = tempPts; } dstPts[1].x = -a3*p[0].x + p[1].x + a3*p[2].x; dstPts[1].y = -a3*p[0].y + p[1].y + a3*p[2].y; dstPts[2].x = a3*p[1].x + p[2].x - a3*p[3].x; dstPts[2].y = a3*p[1].y + p[2].y - a3*p[3].y; dstPts[3] = p[2]; dstPts += 3; } } // // Return the beizer control points corresponding to // the specified row of the mesh. // PointX* Mesh::getMeshRowBeziers( INT row, INT* pointCount, double sx, double sy ) { allocTempPoints(); PointX* ptx = mesh + indexOf(row); for (INT i=0; i < gridCols; i++) { ptxTemp1[i].x = ptx[i].x * sx; ptxTemp1[i].y = ptx[i].y * sy; } INT segments = gridCols-1; spline2Bezier(ptxTemp1, ptxTemp2, segments); *pointCount = 3*segments + 1; return ptxTemp2; } POINT* Mesh::getMeshRowBeziers( INT row, INT* pointCount ) { PointX* ptx; ptx = getMeshRowBeziers(row, pointCount, dstSize.cx - 1, dstSize.cy - 1); PointX::convertToPOINT(ptx, ptTemp, *pointCount); return ptTemp; } // // Return the beizer control points corresponding to // the specified column of the mesh. // PointX* Mesh::getMeshColumnBeziers( INT col, INT* pointCount, double sx, double sy ) { allocTempPoints(); INT i, j; for (i=0, j=col; i < gridRows; i++, j+=gridCols) { ptxTemp1[i].x = mesh[j].x * sx; ptxTemp1[i].y = mesh[j].y * sy; } INT segments = gridRows-1; spline2Bezier(ptxTemp1, ptxTemp2, segments); *pointCount = 3*segments + 1; return ptxTemp2; } POINT* Mesh::getMeshColumnBeziers( INT col, INT* pointCount ) { PointX* ptx; ptx = getMeshColumnBeziers(col, pointCount, dstSize.cx - 1, dstSize.cy - 1); PointX::convertToPOINT(ptxTemp2, ptTemp, *pointCount); return ptTemp; } // // Return the mesh control points for the specified row // POINT* Mesh::getMeshRowPoints( INT row, INT* pointCount ) { allocTempPoints(); POINT* pt = ptTemp; PointX* ptx = mesh + indexOf(row); double sx = dstSize.cx - 1; double sy = dstSize.cy - 1; for (INT j=0; j < gridCols; j++) { pt[j].x = ROUND2INT(ptx[j].x * sx); pt[j].y = ROUND2INT(ptx[j].y * sy); } return pt; } // // Return the specified mesh control point (given row & column) // VOID Mesh::getMeshPoint( INT row, INT col, POINT* point ) { INT index = indexOf(row, col); point->x = ROUND2INT(mesh[index].x * (dstSize.cx - 1)); point->y = ROUND2INT(mesh[index].y * (dstSize.cy - 1)); } // // Set a mesh control point to the specified values // BOOL Mesh::setMeshPoint( INT row, INT col, INT x, INT y ) { // special case for mesh control points along the border if ((row == 0 && y != 0) || (row == gridRows-1 && y != dstSize.cy-1) || (col == 0 && x != 0) || (col == gridCols-1 && x != dstSize.cx-1)) { return FALSE; } double tx, ty; tx = (double) x / (dstSize.cx - 1); ty = (double) y / (dstSize.cy - 1); // quick test to ensure the mesh control point // is well-ordered relative to its four neighbors if (col > 0 && tx <= mesh[indexOf(row, col-1)].x || col < gridCols-1 && tx >= mesh[indexOf(row, col+1)].x || row > 0 && ty <= mesh[indexOf(row-1, col)].y || row < gridRows-1 && ty >= mesh[indexOf(row+1, col)].y) { return FALSE; } INT index = indexOf(row, col); PointX ptx = mesh[index]; mesh[index].x = tx; mesh[index].y = ty; // verify the mesh row and mesh column is single-valued if (verifyRow(row) && verifyColumn(col)) return TRUE; // if not, reject the mesh control point mesh[index] = ptx; return FALSE; } // // Verify that the specified mesh row is well-ordered // BOOL Mesh::verifyRow(INT row) { INT count; PointX* points = getMeshRowBeziers(row, &count, dstSize.cx, dstSize.cy); while (count > 3) { if (!verifyBezierX(points)) return FALSE; points += 3; count -= 3; } return TRUE; } // // Verify that the specified mesh column is well-ordered // BOOL Mesh::verifyColumn(INT col) { INT count; PointX* points = getMeshColumnBeziers(col, &count, dstSize.cx, dstSize.cy); while (count > 3) { if (!verifyBezierY(points)) return FALSE; points += 3; count -= 3; } return TRUE; } // // Check if a Bezier segment is well-ordered in the x-direction // BOOL Mesh::verifyBezierX( PointX* pts ) { double a, b, c, d; // get the quadratic equation for x'(t) a = 3.0 * (3*pts[1].x + pts[3].x - pts[0].x - 3*pts[2].x); b = 6.0 * (pts[0].x - 2*pts[1].x + pts[2].x); c = 3.0 * (pts[1].x - pts[0].x); // solve t for x'(t) = 0 d = b*b - 4*a*c; if (d <= 0 || a == 0) return TRUE; // if both solution are between 0 <= t <= 1 // then the Bezier curve is not well-ordered in x-direction double t1, t2; d = sqrt(d); a = 0.5 / a; t1 = (d - b) * a; t2 = -(d + b) * a; return t1 < 0 || t1 > 1 || t2 < 0 || t2 > 1; } // // Check if a Bezier segment is well-ordered in the y-direction // BOOL Mesh::verifyBezierY( PointX* pts ) { double a, b, c, d; // get the quadratic equation for y'(t) a = 3.0 * (3*pts[1].y + pts[3].y - pts[0].y - 3*pts[2].y); b = 6.0 * (pts[0].y - 2*pts[1].y + pts[2].y); c = 3.0 * (pts[1].y - pts[0].y); // solve t for y'(t) = 0 d = b*b - 4*a*c; if (d <= 0 || a == 0) return TRUE; // if both solution are between 0 <= t <= 1 // then the Bezier curve is not well-ordered in y-direction double t1, t2; d = sqrt(d); a = 0.5 / a; t1 = (d - b) * a; t2 = -(d + b) * a; return t1 < 0 || t1 > 1 || t2 < 0 || t2 > 1; } // // Return a new MeshIterator object so that we can // step along the y-direction and get the scale factors // for each scanline. // MeshIterator* Mesh::getYIterator( INT srcWidth, INT dstWidth, INT ySteps ) { MeshIterator* iterator; iterator = new MeshIterator(srcWidth, dstWidth, ySteps, gridCols); if (iterator == NULL) Error("Out of memory\n"); PointX* ptx; INT count; for (INT col=0; col < gridCols; col++) { ptx = getMeshColumnBeziers(col, &count, dstWidth-1, ySteps-1); // swap x and y coordinates for (INT i=0; i < count; i++) { double t = ptx[i].x; ptx[i].x = ptx[i].y; ptx[i].y = t; } iterator->addCurve(ptx, count); } return iterator; } // // Return a new MeshIterator object so that we can // step along the y-direction and get the scale factors // for each vertical column of pixels. // MeshIterator* Mesh::getXIterator( INT srcHeight, INT dstHeight, INT xSteps ) { MeshIterator* iterator; iterator = new MeshIterator(srcHeight, dstHeight, xSteps, gridRows); if (iterator == NULL) Error("Out of memory\n"); PointX* ptx; INT count; for (INT row=0; row < gridRows; row++) { ptx = getMeshRowBeziers(row, &count, xSteps-1, dstHeight-1); iterator->addCurve(ptx, count); } return iterator; } // // MeshIterator constructor // MeshIterator::MeshIterator( INT srcLen, INT dstLen, INT steps, INT maxCurves ) { if (maxCurves > MAXCURVES) Error("Too many curves in MeshIterator constructor\n"); this->srcLen = srcLen; this->dstLen = dstLen; this->steps = steps; this->maxCurves = maxCurves; curveCount = 0; for (INT i=0; i < MAXCURVES; i++) curves[i] = NULL; } // // MeshIterator - destructor // MeshIterator::~MeshIterator() { for (INT i=0; i < MAXCURVES; i++) delete curves[i]; } // // MeshIterator - add another curve // VOID MeshIterator::addCurve( const PointX* pts, INT count ) { if (curveCount == maxCurves) Error("Program error in MeshIterator::addCurve\n"); FlatCurve* curve = new FlatCurve(pts, count); if (curve == NULL) Error("Out of memory\n"); curves[curveCount++] = curve; } // // MeshIterator - get out positions // VOID MeshIterator::getOutPos( INT index, double* outpos ) { if (curveCount != maxCurves || index < 0 || index >= steps) { Error("Program error in MeshIterator::getOutPos\n"); } INT i, j; double scale; double x = index; for (i=0; i < maxCurves; i++) stops[i] = curves[i]->getPos(x); scale = 1.0 / (srcLen-1); for (i=0; i < srcLen; i++) { j = i * (maxCurves-1) / (srcLen-1); INT i0 = (srcLen-1) * j / (maxCurves-1); if (i == i0) outpos[i] = stops[j]; else { INT i1 = (srcLen-1) * (j+1) / (maxCurves-1); outpos[i] = stops[j] + (stops[j+1] - stops[j]) * (i-i0) / (i1-i0); } } outpos[srcLen] = dstLen; } // // FlatCurve constructor // FlatCurve::FlatCurve( const PointX* pts, INT count ) { capacity = elementCount = 0; allocIncr = count * 3; allocIncr = (allocIncr + 31) & ~31; pointArray = NULL; lastIndex = 0; while (count > 3) { addBezierFlatten(pts); count -= 3; pts += 3; } } // // FlatCurve destructor // FlatCurve::~FlatCurve() { free(pointArray); } // // FlatCurve - add a flattened bezier segment // VOID FlatCurve::addBezierFlatten( const PointX* pts ) { BOOL flatEnough; double dx, dy, distSq; PointX tempPts[4]; static const double epsilon = 0.00001; static const double flatness = 1; static const double flatness2 = flatness*flatness; // // Determine if the Bezier curve is flat enough // // Algorithm // Given 3 points (Ax, Ay), (Bx, By), and (Cx, Cy), // the distance from C to line AB is: // dx = Bx - Ax // dy = By - Ay // L = sqrt(dx*dx + dy*dy) // dist = (dy * (Cx - Ax) - dx * (Cy - Ay))/ L // dx = pts[3].x - pts[0].x; dy = pts[3].y - pts[0].y; distSq = dx*dx + dy*dy; if (distSq < epsilon) { // if P0 and P3 are too close flatEnough = PointX::getSquareDist(pts[0], pts[1]) <= flatness2 && PointX::getSquareDist(pts[2], pts[3]) <= flatness2; } else { // check if P1 is close enough to line P0-P3 double s; s = dy*(pts[1].x - pts[0].x) - dx*(pts[1].y - pts[0].y); s *= s; distSq *= flatness2; if (s > distSq) flatEnough = FALSE; else { // check if P2 is close enough to line P0-P3 s = dy*(pts[2].x - pts[0].x) - dx*(pts[2].y - pts[0].y); flatEnough = (s*s <= distSq); } } // // If Bezier segment is already flat enough, we're done // if (flatEnough) { addLine(pts[0], pts[3]); return; } // // Otherwise, we need to subdivide // tempPts[0] = pts[0]; tempPts[1].x = (pts[0].x + pts[1].x) * 0.5; tempPts[1].y = (pts[0].y + pts[1].y) * 0.5; tempPts[2].x = (pts[0].x + pts[2].x) * 0.25 + pts[1].x * 0.5; tempPts[2].y = (pts[0].y + pts[2].y) * 0.25 + pts[1].y * 0.5; tempPts[3].x = (pts[0].x + pts[3].x) * 0.125 + (pts[1].x + pts[2].x) * 0.375; tempPts[3].y = (pts[0].y + pts[3].y) * 0.125 + (pts[1].y + pts[2].y) * 0.375; addBezierFlatten(tempPts); tempPts[0] = tempPts[3]; tempPts[1].x = (pts[1].x + pts[3].x) * 0.25 + pts[2].x * 0.5; tempPts[1].y = (pts[1].y + pts[3].y) * 0.25 + pts[2].y * 0.5; tempPts[2].x = (pts[2].x + pts[3].x) * 0.5; tempPts[2].y = (pts[2].y + pts[3].y) * 0.5; tempPts[3] = pts[3]; addBezierFlatten(tempPts); } // // FlatCurve - add a line segment // VOID FlatCurve::addLine( const PointX& p1, const PointX& p2 ) { // make sure we have enough space if (capacity < elementCount+2) { capacity += allocIncr; pointArray = (PointX*) realloc(pointArray, capacity*sizeof(PointX)); if (pointArray == NULL) Error("Out of memory\n"); } // add the first end point of the line, if necessary if (elementCount == 0 || p1.x != pointArray[elementCount-1].x || p1.y != pointArray[elementCount-1].y) { pointArray[elementCount++] = p1; } // add the second end point pointArray[elementCount++] = p2; } // // FlatCurve - calculate the value of the curve at a given position // double FlatCurve::getPos( double x ) { while (lastIndex < elementCount && pointArray[lastIndex].x < x) lastIndex++; if (lastIndex == elementCount) return pointArray[elementCount-1].y; if (pointArray[lastIndex].x == x) return pointArray[lastIndex].y; double x0 = pointArray[lastIndex-1].x; double y0 = pointArray[lastIndex-1].y; return y0 + (x - x0) * (pointArray[lastIndex].y - y0) / (pointArray[lastIndex].x - x0); }