#ifdef USE_SVG #include #include #include #include "triangulate.h" /** déclarations : **/ extern char CamcamCCM[]; tsVertex* vertices = NULL; int nvertices = 0; /** déclarations externes : **/ //chemin du répertoire image à générer pour les textures du MAD extern char svgTexturesDir[512]; //id de l'objet SVG courant extern char svgObjectID[260]; //nouveaux points à créer extern char** textureFilenames; //nouveaux points à créer extern std::vector pointsList; //noeud SVG courant extern TiXmlElement* cNode; //liste des textures SVG du document SVG extern SvgTexture* svgTextures; //nombre de textures SVG du document SVG extern int numberOfSvgTextures; //limites de la forme SVG courante extern float svgxmin, svgymin, svgxmax, svgymax, svgz; //centre de l'objet SVG en cours extern PointF objectCenter; //nombre de points composant l'objet SVG extern int svgNbPoints; //nombre d'allocations mémoire extern int nbAlloc; //tableau des tailles des zones mémoire allouées extern unsigned int* allocSizes; //tableau des pointeurs vers les zones mémoire allouées extern void** allocStack; //bounding box du monde SVG à créer extern float svgWorld_Xmin, svgWorld_Ymin, svgWorld_Xmax, svgWorld_Ymax; /** définition des fonctions : **/ /** triangulation de la forme SVG courante en utilisant les coordonnées des points de son contour **/ void triangulatePath(MAD_Simple3DVertex* madPointsList, MAD_GeometricObjectElement* madTrianglesList) { //retournement des coordonnées des vertices en Y en gardant l'ordre trigonométrique des vertices { float swp; int n = pointsList.size(); for(int i=0; i<=(n-1)/2; i++) { //pour les Y pointsList.at(i).y = -pointsList.at(i).y; if(i!=(n-1)/2 || (n+1)%2) { pointsList.at(n-1-i).y = -pointsList.at(n-1-i).y; swp = pointsList.at(i).y; pointsList.at(i).y = pointsList.at(n-1-i).y; pointsList.at(n-1-i).y = swp; } //pour les X swp = pointsList.at(i).x; pointsList.at(i).x = pointsList.at(n-1-i).x; pointsList.at(n-1-i).x = swp; } //retournement en Y des coordonnées du dégradé char fillValue[256] = ""; SvgFillType fillType = svgFillType(cNode->Attribute("fill")); if(fillType!=FILL_EMPTY) strcpy(fillValue, cNode->Attribute("fill")); if(fillType==FILL_URL) { //récupération de l'ID de la texture memmove(fillValue, fillValue+strlen("url(#"), strlen(fillValue)-strlen("url(#")-1); *(fillValue+strlen(fillValue)-strlen("url(#")-1) = 0; //récupération des caractéristiques de la texture for(int i=0; iv[0] = x; v->v[1] = y; v->vnum = vnum++; } nvertices = vnum; } /* Purpose: SCALE_DATA determines the scale for the polygonal data. Parameters: Output, int *XMIN, *XMAX, *YMIN, *YMAX, the minimum and maximum 0 and 1 values of the coordinates of the vertices of the polygon. */ void scale_data() { tsVertex* v; //Compute bounding box for Encapsulated PostScript. v = vertices; svgxmin = v->v[0]; svgxmax = v->v[0]; svgymin = v->v[1]; svgymax = v->v[1]; do { if(v->v[0] > svgxmax) svgxmax = v->v[0]; else if(v->v[0] < svgxmin) svgxmin = v->v[0]; if(v->v[1] > svgymax) svgymax = v->v[1]; else if(v->v[1] < svgymin) svgymin = v->v[1]; v = v->next; } while (v != vertices); //calcul des coordonnées du point de pivot objectCenter = PointF((svgxmax+svgxmin)/2, (svgymax+svgymin)/2); //mise à jour de la bounding box du monde SVG à créer if(svgxminsvgWorld_Xmax) svgWorld_Xmax = svgxmax; if(svgymax>svgWorld_Ymax) svgWorld_Ymax = svgymax; } /* Index a face in the Mad Meshes List */ void indexFace(MAD_GeometricObjectElement* madTrianglesList, tsVertex* v1, tsVertex* v2, tsVertex* v3, int trgInd) { madTrianglesList->Faces[trgInd].Index[2] = v3->vnum; madTrianglesList->Faces[trgInd].Index[1] = v2->vnum; madTrianglesList->Faces[trgInd].Index[0] = v1->vnum; madTrianglesList->Faces[trgInd].UVIndex[2] = v3->vnum; madTrianglesList->Faces[trgInd].UVIndex[1] = v2->vnum; madTrianglesList->Faces[trgInd].UVIndex[0] = v1->vnum; madTrianglesList->Faces[trgInd].SmoothingGroup = MAD_NULL_INDEX; //TO TRY madTrianglesList->Faces[trgInd].MAXflags = 0; //7 = all //TO CHECK : effect under MAX and Camcam } /* Purpose: TRIANGULATE prints N-3 diagonals which triangulate the polygon. Parameters: Input, int XMIN, XMAX, YMIN, YMAX, the minimum and maximum 0 and 1 values of the coordinates of the vertices of the polygon. */ void triangulate(MAD_GeometricObjectElement* madTrianglesList) { //initialisations tsVertex* v0, *v1, *v2, *v3, *v4; /* five consecutive vertices */ int n = nvertices; /* number of vertices; shrinks to 3. */ int trgInd = 0; //remplissage des triangles de la structure Mad madTrianglesList->MaterialID = 0; madTrianglesList->NumberOfTriangles = nvertices-2; madTrianglesList->Faces = (MAD_Face*)malloc2(madTrianglesList->NumberOfTriangles*sizeof(MAD_Face)); memset(madTrianglesList->Faces, 0, madTrianglesList->NumberOfTriangles*sizeof(MAD_Face)); madTrianglesList->NumberOfUsedIndex = svgNbPoints; madTrianglesList->UsedIndex = (unsigned long*)malloc2(svgNbPoints*sizeof(unsigned long)); for(int i=0; iUsedIndex[i] = i; madTrianglesList->OneUVPerVertexPerElementBase = 0; //triangulation ear_init(); //each step of outer loop removes one ear. while(n > 3) { //inner loop searches for an ear. v2 = vertices; do { if(v2->ear) { //ear found. Fill variables. v3 = v2->next; v4 = v3->next; v1 = v2->prev; v0 = v1->prev; //(v1,v3) is a diagonal //ajout du triangle (v1, v2, v3) à la structure MAD indexFace(madTrianglesList, v1, v2, v3, trgInd); trgInd++; //update earity of diagonal endpoints. v1->ear = diagonal(v0, v3); v3->ear = diagonal(v1, v4); //cut off the ear v2. v1->next = v3; v3->prev = v1; vertices = v3; //in case the head was v2. n--; break; } //End if ear found v2 = v2->next; } while(v2 != vertices); } //ajout du dernier triangle (v1, v2, v3) à la structure MAD v2 = v3->next; v3 = v2->next; v1 = v2->prev; indexFace(madTrianglesList, v1, v2, v3, trgInd); trgInd++; } /* Purpose: Calculates the UV coords for texturing the vertices created Parameters: Input, int XMIN, XMAX, YMIN, YMAX, the minimum and maximum 0 and 1 values of the coordinates of the vertices of the polygon. */ void createUVCoords(MAD_Vertex* madUVList) { bool isImage = svgTextures[numberOfSvgTextures-1].type == PNG_IMAGE || svgTextures[numberOfSvgTextures-1].type == JPEG_IMAGE; //on doit assigner les UV pour une image (<-> rectangle) if(isImage) { //remplissage des uv des vertices madUVList[0].x = 1; madUVList[0].y = 1; madUVList[0].z = 1; madUVList[1].x = 1; madUVList[1].y = 0; madUVList[1].z = 1; madUVList[2].x = 0; madUVList[2].y = 0; madUVList[2].z = 1; madUVList[3].x = 0; madUVList[3].y = 1; madUVList[3].z = 1; //retournement verticale de la texture d'une image pour MAX if(TheScene->GetIniValue(CamcamCCM, "svgFlipTexturesFor3DSMAX")) { MAD_Vertex tmp = madUVList[0]; madUVList[0] = madUVList[1]; madUVList[1] = tmp; tmp = madUVList[3]; madUVList[3] = madUVList[2]; madUVList[2] = tmp; } return; } char fillValue[256] = "0"; SvgFillType fillType = svgFillType(cNode->Attribute("fill")); //lecture de la valeur de la donnée de remplissage de la forme if(fillType!=FILL_EMPTY) strcpy(fillValue, cNode->Attribute("fill")); //cas de la texture unicolor if(fillType==FILL_COLOR) { //récupération de la couleur de remplissage int fillColor = (int)atoh(fillValue+1); //remplissage des uv des vertices for(int i=0; i1) prop = 0.999; //affectation des uv pour le vertex M madUVList[i].x = prop; madUVList[i].y = 0; madUVList[i].z = 1; } } break; //TO FINISH: case RADIAL_GRADIENT : { if(svgTextures[i].cx==1e-30) svgTextures[i].cx = objectCenter.x; //TO CHANGE to 50% if(svgTextures[i].cy==1e-30) svgTextures[i].cy = objectCenter.y; //TO CHANGE to 50% if(svgTextures[i].r==1e-30) svgTextures[i].r = 0; //TO CHANGE to 50% if(svgTextures[i].fx==1e-30) svgTextures[i].fx = svgTextures[i].cx; if(svgTextures[i].fy==1e-30) svgTextures[i].fy = svgTextures[i].cy; //calcul de l'équation de la droite directrice du dégradé float x1 = (float)svgTextures[i].cx; float y1 = (float)svgTextures[i].cy; float x2 = (float)svgTextures[i].fx; float y2 = (float)svgTextures[i].fy; //calcul des paramètres de la droite directrice (P1,P2) du dégradé float c = 0; float d = 0; if(x1!=x2) { c = (y1-y2)/(x1-x2); d = y1 - c*x1; } //norme au carré du vecteur P1P2 float nc12 = (x2-x1)*(x2-x1) + (y2-y1)*(y2-y1); //remplissage des uv des vertices for(int i=0; i1) prop = 1; //affectation des uv pour le vertex M madUVList[i].x = prop; madUVList[i].y = 0; madUVList[i].z = 1; } } break; case PNG_IMAGE : { } break; } //sortie de la boucle i = numberOfSvgTextures; } } } } /* création de la texture "path" correspondant à l'image SVG courante */ void createImageTexture(char *textureFilename) { //création du nom du fichier image temporaire PNG ou JPEG char path[260] = ""; if(svgTextures[numberOfSvgTextures-1].type == PNG_IMAGE) sprintf(path, "%s\\%s - texture.png", svgTexturesDir, svgObjectID); else sprintf(path, "%s\\%s - texture.jpeg", svgTexturesDir, svgObjectID); //récupération des données images en base 64 dans le node SVG courant char* data = svgTextures[numberOfSvgTextures-1].data; data = strstr(data, ",") + 1; //décodage des données image de base 64 vers ascii char* cleanedData = getCleanedString(data); char* decodedData = (char*)malloc2(1+strlen(data)); int dataLength = decodeB64(cleanedData, decodedData); free2(cleanedData); free2(svgTextures[numberOfSvgTextures-1].data); svgTextures[numberOfSvgTextures-1].data = decodedData; //écriture du fichier temporaire FILE* outFile = CC_fopen(path, "wb"); fwrite(decodedData, 1, dataLength, outFile); CC_fclose(outFile); //***** à supprimer dès que camcam supportera les fichiers PNG comme texture ***** //vérification de la présence de "nconvert.exe" dans le répertoire "/SVG" char myShareDirPath[] = "\\\\mtp-mauboussin\\Share"; extern HWND hWnd; char Text[2048]; sprintf(Text,"NCONVERT.EXE is missing in the \"/SVG\" folder of the application and is necessary to convert SVG files to MAD files. \nYou should find it in the folder \"%s\".", myShareDirPath); while(lengthOfFile("SVG\\nconvert.exe")==-1) { int userAnswer = MessageBox(GetDesktopWindow(),Text,"Camcam message",MB_RETRYCANCEL|MB_SYSTEMMODAL); if(userAnswer == IDCANCEL) { char svgTexturesDir[] = "SVG\\TMP"; CleanDirectory(svgTexturesDir); RemoveDirectory(svgTexturesDir); exit(-1); } } //écriture de la texture TGA remove(textureFilename); char convertLine[200] = ""; sprintf(convertLine, "\"SVG\\nconvert.exe\" -out tga -o \"%s\" \"%s\"", textureFilename, path); WinExec(convertLine, SW_HIDE); //TO IMPROVE: FILE* filetmp; while(!(filetmp = CC_fopen(textureFilename, "r"))) Sleep(10); CC_fclose(filetmp); //suppression du fichier temporaire remove(path); //*/ } void createShapeTexture(char *path) { //définition de la MAP de texture MAP * sourceMap = (MAP*)malloc2(sizeof(MAP)); //récupération du type de remplissage de la forme SvgFillType fillType = svgFillType(cNode->Attribute("fill")); //cas de la texture unicolor : FILL_EMPTY or FILL_NONE or FILL_COLOR if(fillType!=FILL_URL) { //récupération de la couleur de remplissage int fillColor = 0; if(fillType==FILL_COLOR) { //lecture de la valeur de la donnée de remplissage de la forme char fillValue[256] = ""; strcpy(fillValue, cNode->Attribute("fill")); //transformation en entier de la couleur en hexadécimal fillColor = (int)atoh(fillValue+1); } //création de la MAP de texture sourceMap->SX = sourceMap->SY = sourceMap->PITCH = 8; sourceMap->BASE = (s32*)malloc2(sourceMap->SX*sourceMap->SY*sizeof(s32)); //remplissage de la MAP de texture int color = 255<<24 | fillColor; if(fillType==FILL_NONE) color = 0; for(int pixel=0; pixelSX*sourceMap->SY; pixel++) *(sourceMap->BASE+pixel) = color; } //cas de la texture multicolor else { //lecture de la valeur de la donnée de remplissage de la forme char fillValue[256] = ""; strcpy(fillValue, cNode->Attribute("fill")); //récupération de l'ID de la texture memmove(fillValue, fillValue+strlen("url(#"), strlen(fillValue)-strlen("url(#")-1); *(fillValue+strlen(fillValue)-strlen("url(#")-1) = 0; //récupération des caractéristiques de la texture for(int i=0; iSY = 8; sourceMap->SX = sourceMap->PITCH = OFFSET_PRECISION; sourceMap->BASE = (s32*)malloc2(sourceMap->SX*sourceMap->SY*sizeof(s32)); //remplissage de la MAP de texture int indOffset = 0; for(int px=0; pxSX; px++) { float offsetInf = (float)(svgTextures[i].stopColors[indOffset])/OFFSET_PRECISION; float offsetSup = (float)(svgTextures[i].stopColors[indOffset+2])/OFFSET_PRECISION; float x = ((float)px/OFFSET_PRECISION); float prop = (float)(x-offsetInf)/(float)(offsetSup-offsetInf); int colorInf = svgTextures[i].stopColors[indOffset+1]; int colorSup = svgTextures[i].stopColors[indOffset+3]; int red = prop*(float)(getR(colorSup)-getR(colorInf))+getR(colorInf); int green = prop*(float)(getV(colorSup)-getV(colorInf))+getV(colorInf); int blue = prop*(float)(getB(colorSup)-getB(colorInf))+getB(colorInf); int color = makeRGB(red, green, blue); for(int py=0; pySY; py++) { *(sourceMap->BASE+px+py*sourceMap->PITCH) = 255<<24 | color; } if(x > offsetSup) indOffset+=2; } } break; case RADIAL_GRADIENT : //TO FINISH: break; } //sortie de la boucle i = numberOfSvgTextures; } } } //remplissage de la structure TGA MyTGAHeader header; memset(&header, 0, sizeof(header)); header.uc_ImageTypeCode = 2; header.uw_Width = sourceMap->SX; header.uw_Height = sourceMap->SY; header.uc_BPP = 32; header.ucDescriptorByte = 0x8; //écriture du fichier TGA FILE* f = CC_fopen(path, "wb"); if(f) { fwrite(&header, sizeof(header), 1, f); fwrite(sourceMap->BASE, sizeof(char), sourceMap->SX * sourceMap->SY * 4, f); CC_fclose(f); } //libération des ressources utilisées free2(sourceMap->BASE); free2(sourceMap); } /************************************************************************************/ /* Purpose: DIAGONAL returns TRUE iff (A,B) is a proper internal diagonal of the polygon. Parameters: Input, tsVertex* A, B, two vertices of the polygon. Output, bool DIAGONAL, is TRUE if the line connecting A and B is a proper internal diagonal of the polygon. */ bool diagonal ( tsVertex* a, tsVertex* b ) { return in_cone ( a, b ) && in_cone ( b, a ) && diagonalie ( a, b ); } /* Purpose: MAKE_NULL_VERTEX makes a vertex. Parameters: Output, tsVertex* MAKE_NULL_VERTEX, the new vertex. */ tsVertex* make_null_vertex ( void ) { tsVertex* v; v = ( tsVertex * ) malloc2 ( sizeof ( tsVertex ) ); if(vertices) { v->next = vertices; v->prev = vertices->prev; vertices->prev = v; v->prev->next = v; } else { vertices = v; vertices->next = v; vertices->prev = v; } return v; } /* Purpose: EAR_INIT initializes the data structures, and calls Triangulate2 to clip ears. Local Parameters: Local, tsVertex* V0, V1, V2, three consecutive vertices of the polygon. */ void ear_init ( void ) { tsVertex* v0; tsVertex* v1; tsVertex* v2; /* Initialize v1->ear for all vertices. */ v1 = vertices; do { v2 = v1->next; v0 = v1->prev; v1->ear = diagonal ( v0, v2 ); v1 = v1->next; } while ( v1 != vertices ); return; } /* Purpose: DIAGONALIE returns TRUE iff (A,B) is a proper diagonal of a polygon. Discussion: (A,B) may be an internal or external diagonal of the polygon, ignoring edges incident to A and B. Parameters: Input, tsVertex* A, B, two vertices of the polygon. Output, bool DIAGONALIE, is TRUE if the line connecting A and B is a proper diagonal of the polygon. */ bool diagonalie ( tsVertex* a, tsVertex* b ) { tsVertex* c; tsVertex* c1; /* For each edge (C,C1) of P. */ c = vertices; do { c1 = c->next; /* Skip edges incident to A or B. */ if ( ( c != a ) && ( c1 != a ) && ( c != b ) && ( c1 != b ) && intersect( a->v, b->v, c->v, c1->v ) ) { return false; } c = c->next; } while ( c != vertices ); return true; } /* Purpose: IN_CONE returns TRUE iff the diagonal (A,B) is strictly internal. Discussion: More correctly, the diagonal (A,B) must be strictly internal to the polygon in the neighborhood of the A endpoint. Parameters: Input, tsVertex* A, B, two vertices of the polygon. Output, bool IN_CONE, is TRUE if the line connecting A and B is strictly internal to the polygon in the neighborhood of A. Local Parameters: Local, tsVertex* A0, A1, are the vertices before and after A. */ bool in_cone ( tsVertex* a, tsVertex* b ) { tsVertex* a0; tsVertex* a1; a1 = a->next; a0 = a->prev; /* If A is a convex vertex ... */ if ( left_on ( a->v, a1->v, a0->v ) ) { return left ( a->v, b->v, a0->v ) && left ( b->v, a->v, a1->v ); } /* Else A is reflex vertex: */ else { return !( left_on ( a->v, b->v, a1->v ) && left_on ( b->v, a->v, a0->v ) ); } } float area_poly2 ( void ) { tsVertex* a; tsVertex* p; float sum = 0; p = vertices; /* Fixed. */ a = p->next; /* Moving. */ do { sum = sum + area2 ( p->v, a->v, a->next->v ); a = a->next; } while ( a->next != vertices ); return sum; } /* Purpose: AREA_SIGN returns the sign of the area defined by three points. Parameters: Input, tPointi A, B, C, three points that define a triangle. Output, int AREA_SIGN, the sign of the area of the triangle. */ int area_sign ( tPointi a, tPointi b, tPointi c ) { float area; area = ( b[0] - a[0] ) * ( c[1] - a[1] ) - ( c[0] - a[0] ) * ( b[1] - a[1] ); /* The area should be an integer. */ if ( area > 0 ) { return 1; } else if ( area < 0 ) { return -1; } else { return 0; } } /* Purpose: AREA2 returns twice the signed area of a triangle. Discussion: The area is positive if points A, B, and C are oriented counter clockwise, negative if clockwise, and zero if the points are collinear. Parameters: Input, tPointi A, B, C, three points that define a triangle. Output, int AREA2, twice the signed area of the triangle. */ float area2 ( tPointi a, tPointi b, tPointi c ) { return (( b[0] - a[0] ) * ( c[1] - a[1] ) - ( c[0] - a[0] ) * ( b[1] - a[1] )); } /* Purpose: BETWEEN returns TRUE iff point C lies on the closed segement AB. Discussion: The function first checks that C is collinear with A and B. Parameters: Input, tPointi A, B, C, three points to be tested. Output, bool BETWEEN is TRUE if point C lies on the closed segment between A and B. */ bool between ( tPointi a, tPointi b, tPointi c ) { if ( ! collinear ( a, b, c ) ) { return false; } /* If AB not vertical, check betweenness on x; else on y. */ if ( a[0] != b[0] ) { return ( (a[0] <= c[0] ) && ( c[0] <= b[0] ) ) || ( (a[0] >= c[0] ) && ( c[0] >= b[0] ) ); } else { return ( ( a[1] <= c[1] ) && ( c[1] <= b[1] ) ) || ( ( a[1] >= c[1] ) && ( c[1] >= b[1] ) ); } } /* Purpose: COLLINEAR is TRUE if the points A, B and C are collinear. Parameters: Input, tPointi A, B, C, three points to be tested. Output, bool COLLINEAR, is TRUE if the points are collinear. */ bool collinear ( tPointi a, tPointi b, tPointi c ) { return ( area_sign ( a, b, c ) == 0 ); } /* Purpose: INTERSECT returns TRUE iff segments AB and CD intersect. Discussion: The intersection may be proper or improper. Parameters: Input, tPointi A, B, C, D, points that define the segments. Output, bool INTERSECT, is TRUE if segments AB and CD intersect. */ bool intersect ( tPointi a, tPointi b, tPointi c, tPointi d ) { if ( intersect_prop ( a, b, c, d ) ) { return true; } else if ( between ( a, b, c ) || between ( a, b, d ) || between ( c, d, a ) || between ( c, d, b ) ) { return true; } else { return false; } } /* Purpose: INTERSECT_PROP returns true if and only if AB properly intersects CD. Discussion: AB and CD must share a point interior to both segments. The properness of the intersection is ensured by using strict leftness. Parameters: Input, tPointi A, B, C, D, points that define segments AB and CD. Output, bool INTERSECT_PROP, is TRUE if AB properly intersects CD. */ bool intersect_prop ( tPointi a, tPointi b, tPointi c, tPointi d ) { /* Eliminate improper cases. */ if ( collinear ( a, b, c ) || collinear ( a, b, d ) || collinear ( c, d, a ) || collinear ( c, d, b ) ) { return false; } return xor ( left ( a, b, c ), left ( a, b, d ) ) && xor ( left ( c, d, a ), left ( c, d, b ) ); } /* Purpose: LEFT is TRUE if C is on the left side of the line from A to B. Discussion: More correctly, the function returns true if and only if C is strictly to the left of the directed line through A to B. Parameters: Input, tPointi A, B, C, three points to be tested. Output, bool LEFT, is TRUE if C is strictly to the left of the directed line from A to B. */ bool left ( tPointi a, tPointi b, tPointi c ) { return ( area_sign ( a, b, c ) > 0 ); } /* Purpose: LEFT_ON is TRUE if C is to the left side, or on, the line from A to B. Parameters: Input, tPointi A, B, C, three points to be tested. Output, bool LEFT_ON, is TRUE if C is strictly to the left of, or on, the directed line from A to B. */ bool left_on ( tPointi a, tPointi b, tPointi c ) { return ( area_sign ( a, b, c ) >= 0 ); } /* Purpose: PRINT_POLY prints the polygon data. */ void print_poly ( void ) { tsVertex* v; printf ( "%%\n" ); printf ( "%%Polygon circular list:\n" ); printf ( "%%\n" ); v = vertices; do { printf( "%% vnum=%5d: ear=%d\n", v->vnum, v->ear ); v = v->next; } while ( v != vertices ); printf ( "%%\n" ); return; } /* Purpose: PRINT_VERTICES prints the vertices. Discussion: This function uses the VNUM indices corresponding to the order in which the vertices were input. The output is in PostScript format. Parameters: Input, int XMIN, XMAX, YMIN, YMAX, the minimum and maximum 0 and 1 values of the coordinates of the vertices of the polygon. Input, int SCALE, an appropriate scaling for the data. */ void print_vertices ( float xmin, float xmax, float ymin, float ymax, int scale ) { /* Pointers to vertices, edges, faces. */ tsVertex* v; int x; int y; /* PostScript header */ printf ( "%%!PS\n" ); printf ( "%%%%Creator: triangulate.c (Joseph O'Rourke)\n" ); printf ( "%%%%BoundingBox: %d %d %d %d\n", 0, 0, 72 + scale * ( xmax - xmin ), 72 + scale * ( ymax - ymin ) ); printf ( "%%%%EndComments\n" ); printf ( "1 1 setlinewidth\n" ); /* Output vertex information as a PostScript comment. */ printf ( "\n" ); printf ( "%% number of vertices = %d\n", nvertices ); v = vertices; do { printf ( "%% vnum=%5d:\tx=%5d\ty=%5d\n", v->vnum, v->v[0], v->v[1] ); v = v->next; } while ( v != vertices ); /* Draw the polygon. */ printf ( "\n%%Polygon:\n" ); printf ( "newpath\n" ); v = vertices; x = 36 + scale * ( v->v[0] - xmin ); y = 36 + scale * ( v->v[1] - ymin ); printf ( "%d\t%d\tmoveto\n", x, y ); v = v->next; do { x = 36 + scale * ( v->v[0] - xmin ); y = 36 + scale * ( v->v[1] - ymin ); printf ( "%d\t%d\tlineto\n", x, y ); v = v->next; } while ( v != vertices ); printf ( "closepath stroke\n" ); } bool xor ( bool x, bool y ) { return ( !x ^ !y ); } #endif //USE_SVG