/***************************************************************************** * Filter to convert IRIT data files to NFF format. * * * * Written by: Gershon Elber Ver 1.0, Jan 1992 * *****************************************************************************/ #ifdef __MSDOS__ #include #include #endif /* __MSDOS__ */ #include #include #include #include #include "irit_sm.h" #include "iritprsr.h" #include "getarg.h" #include "genmat.h" #define DEFAULT_KD " 1.0" #define DEFAULT_KS " 0.0" #define DEFAULT_SHINE " 0.0" #define DEFAULT_TRANS " 0.0" #define DEFAULT_INDEX " 0.0" #define DEFAULT_COLOR " 1.0 1.0 1.0" #define DIST_EPSILON 2e-4 #define SIZE_EPSILON 1e-5 #define CONVEX_EPSILON 1e-3 #define STRCAT2(Str1, Str2, Str3) strcat(strcat(Str1, Str2), Str3) #ifdef __MSDOS__ extern unsigned int _stklen = 32766; /* Increase default stack size. */ #endif /* __MSDOS__ */ #ifdef NO_CONCAT_STR static char *VersionStr = "Irit2Nff Version 3.0, Gershon Elber,\n\ (C) Copyright 1989/90/91 Gershon Elber, Non commercial use only."; #else static char *VersionStr = "Irit2Nff " VERSION ", Gershon Elber, " __DATE__ ", " __TIME__ "\n" "(C) Copyright 1989/90/91 Gershon Elber, Non commercial use only."; #endif /* NO_CONCAT_STR */ static char *CtrlStr = "Irit2Nff l%- 4%- c%- f%-FineNess!d o%-OutName!s g%- z%- DFiles!*s"; static char *OutFileName = "Irit2Nff"; static int GlblCPPSupport = FALSE, GlblFineNess = 5, GlblDumpOnlyGeometry = FALSE, FourPerFlat = FALSE; static MatrixType CrntViewMat; /* This is the current view! */ static int TransColorTable[][4] = { { /* BLACK */ 0, 0, 0, 0 }, { /* BLUE */ 1, 0, 0, 255 }, { /* GREEN */ 2, 0, 255, 0 }, { /* CYAN */ 3, 0, 255, 255 }, { /* RED */ 4, 255, 0, 0 }, { /* MAGENTA */ 5, 255, 0, 255 }, { /* BROWN */ 6, 50, 0, 0 }, { /* LIGHTGRAY */ 7, 127, 127, 127 }, { /* DARKGRAY */ 8, 63, 63, 63 }, { /* LIGHTBLUE */ 9, 0, 0, 255 }, { /* LIGHTGREEN */ 10, 0, 255, 0 }, { /* LIGHTCYAN */ 11, 0, 255, 255 }, { /* LIGHTRED */ 12, 255, 0, 0 }, { /* LIGHTMAGENTA */ 13, 255, 0, 255 }, { /* YELLOW */ 14, 255, 255, 0 }, { /* WHITE */ 15, 255, 255, 255 }, { /* BROWN */ 20, 50, 0, 0 }, { /* DARKGRAY */ 56, 63, 63, 63 }, { /* LIGHTBLUE */ 57, 0, 0, 255 }, { /* LIGHTGREEN */ 58, 0, 255, 0 }, { /* LIGHTCYAN */ 59, 0, 255, 255 }, { /* LIGHTRED */ 60, 255, 0, 0 }, { /* LIGHTMAGENTA */ 61, 255, 0, 255 }, { /* YELLOW */ 62, 255, 255, 0 }, { /* WHITE */ 63, 255, 255, 255 }, { -1, 0, 0, 0 } }; static IPObjectStruct *MainGetDataFiles(char **DataFileNames, int NumOfDataFiles); static IPPolygonStruct *Surface2Polygons(CagdSrfStruct *Srf, int FourPerFlat, int FineNess); static void DumpDataForNFF(IPObjectStruct *PObjects); static int DumpOneObject(FILE *FRay, FILE *FGeom, IPObjectStruct *PObject); static int DumpOnePolygon(FILE *f, IPPolygonStruct *PPolygon); static int IsConvexPolygon(IPPolygonStruct *Pl); static RealType *MapPoint(RealType *Pt); static RealType *MapVector(RealType *Pt, RealType *Vec); static void MyExit(int ExitCode); /***************************************************************************** * Main routine - Read Parameter line and do what you need... * *****************************************************************************/ void main(int argc, char **argv) { int Error, FineNessFlag = FALSE, LinearOnePolyFlag = FALSE, VerFlag = FALSE, OutFileFlag = FALSE, NumFiles = 0; char Line[LINE_LEN_LONG], *p, **FileNames = NULL; IPObjectStruct *PObjects; #ifdef __MSDOS__ ctrlbrk((int (*)()) MyExit); /* Kill process if ^C. */ #endif /* __MSDOS__ */ if ((Error = GAGetArgs (argc, argv, CtrlStr, &LinearOnePolyFlag, &FourPerFlat, &GlblCPPSupport, &FineNessFlag, &GlblFineNess, &OutFileFlag, &OutFileName, &GlblDumpOnlyGeometry, &VerFlag, &NumFiles, &FileNames)) != 0) { GAPrintErrMsg(Error); GAPrintHowTo(CtrlStr); MyExit(1); } if (VerFlag) { fprintf(stderr, "\n%s\n\n", VersionStr); GAPrintHowTo(CtrlStr); MyExit(0); } if (LinearOnePolyFlag) { fprintf(stderr, "Linear patch side will have a single polygon.\n"); CagdSetLinear2Poly(CAGD_ONE_POLY_PER_COLIN); } else CagdSetLinear2Poly(CAGD_REG_POLY_PER_LIN); fprintf(stderr, "%s triangles per flat will be created.\n", FourPerFlat ? "Four" : "Two"); if (GlblDumpOnlyGeometry && !GlblCPPSupport) { fprintf(stderr, "Flag -g makes sense only with -c.\n"); MyExit(1); } if (!NumFiles) { fprintf(stderr, "No data file names where given, exit.\n"); GAPrintHowTo(CtrlStr); MyExit(1); } if (!OutFileFlag) { /* Pick the first input name as output name. */ strcpy(Line, FileNames[0]); if ((p = strrchr(Line, '.')) != NULL); /* Remove old file type. */ *p = 0; OutFileName = malloc(strlen(Line) + 1); strcpy(OutFileName, Line); } /* Get the data files: */ IritPrsrPolyListCirc = FALSE; PObjects = MainGetDataFiles(FileNames, NumFiles); if (IritPrsrWasPrspMat) MultTwo4by4(CrntViewMat, IritPrsrViewMat, IritPrsrPrspMat); else GEN_COPY(CrntViewMat, IritPrsrViewMat, sizeof(MatrixType)); DumpDataForNFF(PObjects); MyExit(0); } /***************************************************************************** * Main routine to read the data description files: * * Returns pointer to pointers on FileDescription structures (one per file). * *****************************************************************************/ static IPObjectStruct *MainGetDataFiles(char **DataFileNames, int NumOfDataFiles) { int i; FILE *f; char *ErrorMsg = NULL; IPObjectStruct *PObj, *PObjTail, *PObjHead = NULL; for (i = 0; i < NumOfDataFiles; i++) { #ifdef __MSDOS__ if ((f = fopen(*DataFileNames, "rt")) == NULL) { /* Open the file. */ #else if ((f = fopen(*DataFileNames, "r")) == NULL) { /* Open the file. */ #endif /* __MSDOS__ */ fprintf(stderr, "Can't open data file %s\n", *DataFileNames); MyExit(1); } if ((PObj = IritPrsrGetObjects(f)) != NULL) { /* Get the data file. */ PObjTail = PObj; while (PObjTail -> Pnext) PObjTail = PObjTail -> Pnext; PObjTail -> Pnext = PObjHead; PObjHead = PObj; } fclose(f); /* Close the file. */ if (IritPrsrParseError(&ErrorMsg)) { fprintf(stderr, "Parse error in \"%s\":\n%s\n", *DataFileNames, ErrorMsg); MyExit(1); } DataFileNames++; /* Skip to next file name. */ } if (PObjHead == NULL) { fprintf(stderr, "No data found.\n"); MyExit(1); } return PObjHead; } /***************************************************************************** * Routine to convert all surfaces/curves into polylines as follows: * * Curves are converted to single polyline with SamplesPerCurve samples. * * Surface are converted into GlblNumOfIsolines curves in each axes, each * * handled as Curves above. The curves and surfaces are then deleted. * *****************************************************************************/ IPObjectStruct *IritPrsrProcessFreeForm(IPObjectStruct *CrvObjs, IPObjectStruct *SrfObjs) { int LocalFourPerFlat; float RelativeFineNess; CagdCrvStruct *Crvs; CagdSrfStruct *Srf, *Srfs; IPObjectStruct *PObj, *PObjNext; IPPolygonStruct *PPolygon, *PPolygonTemp; if (CrvObjs == NULL && SrfObjs == NULL) return NULL; /* Make sure requested format is something reasonable. */ if (GlblFineNess < 2) { GlblFineNess = 2; fprintf(stderr, "FineNess is less than 2, 2 picked instead.\n"); } if (CrvObjs) { /* Curves are not rendered at this time and they are ignored. */ for (PObj = CrvObjs; PObj != NULL;) { Crvs = PObj -> U.PCrvs; CagdCrvFreeList(Crvs); PObjNext = PObj -> Pnext; free((VoidPtr) PObj); PObj = PObjNext; } CrvObjs = NULL; } if (SrfObjs) { for (PObj = SrfObjs; PObj != NULL; PObj = PObj -> Pnext) { char *p; Srfs = PObj -> U.PSrfs; PObj -> U.PPolygon = NULL; RelativeFineNess = 1.0; LocalFourPerFlat = FourPerFlat; if (IritPrsrGetStrAttrib(PObj, "twoperflat")) LocalFourPerFlat = FALSE; if (IritPrsrGetStrAttrib(PObj, "fourperflat")) LocalFourPerFlat = TRUE; if ((p = IritPrsrGetStrAttrib(PObj, "resolution")) != NULL && sscanf(p, "%f", &RelativeFineNess) != 1) RelativeFineNess = 1.0; for (Srf = Srfs; Srf != NULL; Srf = Srf -> Pnext) { PPolygon = PPolygonTemp = Surface2Polygons(Srf, LocalFourPerFlat, (int) (RelativeFineNess * GlblFineNess)); while (PPolygonTemp -> Pnext) PPolygonTemp = PPolygonTemp -> Pnext; PPolygonTemp -> Pnext = PObj -> U.PPolygon; PObj -> U.PPolygon = PPolygon; } CagdSrfFreeList(Srfs); } } return SrfObjs; } /***************************************************************************** * Routine to convert a single surface into a polylines with SamplesPerCurve * * samples, NumOfIsolines isolines into a polyline object list. * *****************************************************************************/ static IPPolygonStruct *Surface2Polygons(CagdSrfStruct *Srf, int FourPerFlat, int FineNess) { int i, j; IPVertexStruct *V, *VHead, *VTail = NULL; IPPolygonStruct *P, *PHead = NULL; CagdPolygonStruct *CagdPolygon, *CagdPolygonHead = CagdSrf2Polygons(Srf, FineNess, TRUE, FourPerFlat); for (CagdPolygon = CagdPolygonHead, VHead = NULL; CagdPolygon != NULL; CagdPolygon = CagdPolygon -> Pnext) { /* All polygons are triangles! */ for (i = 0, VHead = NULL; i < 3; i++) { /* Convert to vertices. */ V = IritPrsrNewVertexStruct(); IP_SET_VRTX_NORMAL(V); /* This vertex has normal. */ for (j = 0; j < 3; j++) /* Convert to our format. */ V -> Coord[j] = CagdPolygon -> Polygon[i].Pt[j]; for (j = 0; j < 3; j++) V -> Normal[j] = CagdPolygon -> Normal[i].Vec[j]; if (VHead) { VTail -> Pnext = V; VTail = V; } else VHead = VTail = V; } P = IritPrsrNewPolygonStruct(); P -> PVertex = VHead; P -> Type = IP_POLYGON; P -> Pnext = PHead; PHead = P; } CagdPolygonFreeList(CagdPolygonHead); return PHead; } /***************************************************************************** * Dumps the data for NFF into stdout. * *****************************************************************************/ static void DumpDataForNFF(IPObjectStruct *PObjects) { static char *Header1[] = { "#", "# This file was automatically created from IRIT solid modeller data", "# using Irit2Nff - IRIT to NFF filter.", "#", "# (c) Copyright 1991/92 Gershon Elber, Non commercial use only.", "#", NULL }; static char *Header2[] = { "", "v", "from 0 0 10", "at 0 0 0", "up 0 1 0", "angle 12", "resolution 512 512", "", "l 1 1 1", "", NULL }; int i, TotalPolys = 0; char Line[128]; IPObjectStruct *PObj, *PObjHead = NULL; FILE *FGeom, *FNff; sprintf(Line, "%s.nff", OutFileName); if (!GlblDumpOnlyGeometry) { if ((FNff = fopen(Line, "w")) == NULL) { fprintf(stderr, "Failed to open \"%s\".\n", Line); exit(2); } } else FNff = NULL; if (GlblCPPSupport) { /* Separated files for geometry/surface quality. */ sprintf(Line, "%s.geom", OutFileName); if ((FGeom = fopen(Line, "w")) == NULL) { fprintf(stderr, "Failed to open \"%s\".\n", Line); exit(2); } } else FGeom = NULL; if (FNff != NULL) { if (GlblCPPSupport) fprintf(FNff, "/*\n"); for (i = 0; Header1[i] != NULL; i++) fprintf(FNff, "%s\n", Header1[i]); if (GlblCPPSupport) fprintf(FNff, "*/\n"); fprintf(FNff, "\n"); for (i = 0; Header2[i] != NULL; i++) fprintf(FNff, "%s\n", Header2[i]); } if (GlblCPPSupport) { fprintf(FGeom, "/*\n"); for (i = 0; Header1[i] != NULL; i++) fprintf(FGeom, "%s\n", Header1[i]); fprintf(FGeom, "*/\n\n"); } /* Reverse object list since it was loaded in reverse by iritprsr module.*/ while (PObjects != NULL) { PObj = PObjects; PObjects = PObjects -> Pnext; PObj -> Pnext = PObjHead; PObjHead = PObj; } PObjects = PObjHead; while (PObjects) { TotalPolys += DumpOneObject(FNff, FGeom, PObjects); PObjects = PObjects -> Pnext; } if (GlblCPPSupport && FNff != NULL) fprintf(FNff, "#include \"%s\"\n", Line); if (FNff != NULL) fclose(FNff); if (FGeom != NULL) fclose(FGeom); fprintf(stderr, "\nTotal number of polygons - %d\n", TotalPolys); } /***************************************************************************** * Routine to dump one object PObject. * *****************************************************************************/ static int DumpOneObject(FILE *FNff, FILE *FGeom, IPObjectStruct *PObject) { static int ObjectSeqNum = 1; int i, j, PolyCount = 0, HasColor = FALSE; char *p, Name[LINE_LEN], SrfPropString[LINE_LEN_LONG]; RealType RGBColor[3]; IPPolygonStruct *PList = PObject -> U.PPolygon; if (strlen(PObject -> Name) == 0) sprintf(Name, "ObjSeq%d", ObjectSeqNum); else strcpy(Name, PObject -> Name); SrfPropString[0] = 0; if ((p = IritPrsrGetStrAttrib(PObject, "kd")) != NULL) STRCAT2(SrfPropString, " ", p); else strcat(SrfPropString, DEFAULT_KD); if ((p = IritPrsrGetStrAttrib(PObject, "ks")) != NULL) STRCAT2(SrfPropString, " ", p); else strcat(SrfPropString, DEFAULT_KS); if ((p = IritPrsrGetStrAttrib(PObject, "shine")) != NULL) STRCAT2(SrfPropString, " ", p); else strcat(SrfPropString, DEFAULT_SHINE); if ((p = IritPrsrGetStrAttrib(PObject, "trans")) != NULL) STRCAT2(SrfPropString, " ", p); else strcat(SrfPropString, DEFAULT_TRANS); if ((p = IritPrsrGetStrAttrib(PObject, "index")) != NULL) STRCAT2(SrfPropString, " ", p); else strcat(SrfPropString, DEFAULT_INDEX); if ((p = IritPrsrGetStrAttrib(PObject, "rgb")) != NULL) { # ifdef __MSDOS__ HasColor = sscanf(p, "%f,%f,%f", # else HasColor = sscanf(p, "%lf,%lf,%lf", # endif /* __MSDOS__ */ &RGBColor[0], &RGBColor[1], &RGBColor[2]) == 3; } else if (IP_HAS_OBJ_RGB(PObject)) { HasColor = TRUE; for (i = 0; i < 3; i++) RGBColor[i] = PObject -> RGB[i]; } else if (IP_HAS_OBJ_COLOR(PObject)) { for (i = 0; TransColorTable[i][0] >= 0; i++) { if (TransColorTable[i][0] == PObject -> Color) { HasColor = TRUE; for (j = 0; j < 3; j++) RGBColor[j] = TransColorTable[i][j+1]; break; } } } if (FNff != NULL) fprintf(FNff, GlblCPPSupport ? "/*\n#\n# %s\n#\n*/\n" : "#\n# %s\n#\n", Name); if (GlblCPPSupport) { if (FNff != NULL) fprintf(FNff, "#define %s_SRF_PROP ", Name); fprintf(FGeom, "/*\n#\n# %s\n#\n*/\n", Name); fprintf(FGeom, "f %s_SRF_PROP\n", Name); } else { if (FNff != NULL) fprintf(FNff, "f "); } if (HasColor) { for (i = 0; i < 3; i++) RGBColor[i] /= 255.0; if (FNff != NULL) fprintf(FNff, "%7.4lf %7.4lf %7.4lf", RGBColor[0], RGBColor[1], RGBColor[2]); } else { if (FNff != NULL) fprintf(FNff, "%s", DEFAULT_COLOR); } if (FNff != NULL) fprintf(FNff, " %s\n\n", SrfPropString); while (PList) { PolyCount += DumpOnePolygon(GlblCPPSupport ? FGeom : FNff, PList); PList = PList -> Pnext; } fprintf(stderr, "Processing \"%s\" - %d triangles.\n", Name, PolyCount); if (FNff != NULL) fprintf(FNff, "\n\n"); if (GlblCPPSupport) fprintf(FGeom, "\n\n"); ObjectSeqNum++; return PolyCount; } /***************************************************************************** * Routine to dump one polygon, using global Matrix transform CrntViewMat. * *****************************************************************************/ static int DumpOnePolygon(FILE *Fl, IPPolygonStruct *PPolygon) { int i, TriCount = 0; RealType *MappedNormal[3], *MappedPoint[3], Normal[3], Vec1[3], Vec2[3]; IPVertexStruct *VFirst, *V1, *V2, *VList = PPolygon -> PVertex; if (Fl == NULL || VList == NULL) return 0; if (!IsConvexPolygon(PPolygon)) { static int Printed = FALSE; if (!Printed) { fprintf(stderr, "Non convex polygon(s) may be in data (see CONVEX in IRIT).\n"); Printed = TRUE; } } switch (PPolygon -> Type) { case IP_POLYGON: VFirst = VList; V1 = VFirst -> Pnext; V2 = V1 -> Pnext; while (V2 != NULL) { MappedPoint[0] = MapPoint(VFirst -> Coord); MappedPoint[1] = MapPoint(V1 -> Coord); MappedPoint[2] = MapPoint(V2 -> Coord); /* Test for two type of degeneracies. Make sure that no two */ /* points in the triangle are the same and that they are */ /* not colinear. */ if (!PT_EQ(MappedPoint[0], MappedPoint[1]) && !PT_EQ(MappedPoint[0], MappedPoint[2]) && !PT_EQ(MappedPoint[1], MappedPoint[2])) { PT_SUB(Vec1, MappedPoint[0], MappedPoint[1]); PT_SUB(Vec2, MappedPoint[1], MappedPoint[2]); PT_NORMALIZE(Vec1); PT_NORMALIZE(Vec2); CROSS_PROD(Normal, Vec1, Vec2); if (PT_LENGTH(Normal) > SIZE_EPSILON) { PT_NORMALIZE(Normal); MappedNormal[0] = MapVector(VFirst -> Coord, VFirst -> Normal); MappedNormal[1] = MapVector(V1 -> Coord, V1 -> Normal); MappedNormal[2] = MapVector(V2 -> Coord, V2 -> Normal); if (DOT_PROD(Normal, MappedNormal[0]) < -SIZE_EPSILON || DOT_PROD(Normal, MappedNormal[1]) < -SIZE_EPSILON || DOT_PROD(Normal, MappedNormal[2]) < -SIZE_EPSILON) { SWAP(RealType *, MappedPoint[1], MappedPoint[2]); SWAP(RealType *, MappedNormal[1], MappedNormal[2]); PT_SCALE(Normal, -1.0); } /* Make sure all normals are set properly: */ if (DOT_PROD(MappedNormal[0], MappedNormal[0]) < SIZE_EPSILON) PT_COPY(MappedNormal[0], Normal); if (DOT_PROD(MappedNormal[1], MappedNormal[1]) < SIZE_EPSILON) PT_COPY(MappedNormal[1], Normal); if (DOT_PROD(MappedNormal[2], MappedNormal[2]) < SIZE_EPSILON) PT_COPY(MappedNormal[2], Normal); TriCount++; fprintf(Fl, "pp 3\n"); for (i = 0; i < 3; i++) fprintf(Fl, " %10.7lf %10.7lf %10.7lf %9.6lf %9.6lf %9.6lf\n", MappedPoint[i][0], MappedPoint[i][1], MappedPoint[i][2], MappedNormal[i][0], MappedNormal[i][1], MappedNormal[i][2]); } } V1 = V2; V2 = V2 -> Pnext; } break; } return TriCount; } /***************************************************************************** * Routine to test if the given polygon is convex or not. * * Algorithm: The polygon is convex iff the normals generated from cross * * products of two consecutive edges points to the same direction. The same * * direction is tested by a positive dot product. * *****************************************************************************/ static int IsConvexPolygon(IPPolygonStruct *Pl) { RealType Size, V1[3], V2[3], LastNormal[3], Normal[3]; IPVertexStruct *VNext, *VNextNext, *V = Pl -> PVertex; LastNormal[0] = LastNormal[1] = LastNormal[2] = 0.0; do { if ((VNext = V -> Pnext) == NULL) VNext = Pl -> PVertex; if ((VNextNext = VNext -> Pnext) == NULL) VNextNext = Pl -> PVertex; PT_SUB(V1, VNext -> Coord, V -> Coord); if ((Size = PT_LENGTH(V1)) > EPSILON) { Size = 1.0 / Size; PT_SCALE(V1, Size); } PT_SUB(V2, VNextNext -> Coord, VNext -> Coord); if ((Size = PT_LENGTH(V2)) > EPSILON) { Size = 1.0 / Size; PT_SCALE(V2, Size); } CROSS_PROD(Normal, V1, V2); if (V != Pl -> PVertex) { if (PT_LENGTH(Normal) > CONVEX_EPSILON && DOT_PROD(Normal, LastNormal) < -CONVEX_EPSILON) return FALSE; } PT_COPY(LastNormal, Normal); V = VNext; } while (V != Pl -> PVertex && V != NULL); return TRUE; } /***************************************************************************** * Maps the given E3 point using the CrntViewMat. * *****************************************************************************/ static RealType *MapPoint(RealType *Pt) { static int Count = 0; static RealType MappedPts[3][3]; RealType *MappedPt = MappedPts[Count++]; if (Count >= 3) Count = 0; MultVecby4by4(MappedPt, Pt, CrntViewMat); return MappedPt; } /***************************************************************************** * Maps the given E3 vector using the CrntViewMat. * * This routine will return a zero vector if normal is not computable. * *****************************************************************************/ static RealType *MapVector(RealType *Pt, RealType *Vec) { static int Count = 0, WasWarning = 0; static RealType MappedVecs[3][3]; RealType MappedPt[3], Pt2[3], MappedPt2[3], *MappedVec = MappedVecs[Count++]; if (Count >= 3) Count = 0; if (DOT_PROD(Vec, Vec) < SIZE_EPSILON) { MappedVec[0] = MappedVec[1] = MappedVec[2] = 0.0; if (!WasWarning) { WasWarning = 1; fprintf(stderr, "Non computable normals detected. Approximated from geometry.\n"); } } else { MultVecby4by4(MappedPt, Pt, CrntViewMat); PT_ADD(Pt2, Pt, Vec); MultVecby4by4(MappedPt2, Pt2, CrntViewMat); PT_SUB(MappedVec, MappedPt2, MappedPt); PT_NORMALIZE(MappedVec); } return MappedVec; } /***************************************************************************** * Trap Cagd_lib errors right here. * *****************************************************************************/ void CagdFatalError(CagdFatalErrorType ErrID) { char *ErrorMsg = CagdDescribeError(ErrID); fprintf(stderr, "CAGD_LIB: %s", ErrorMsg); exit(-1); } /***************************************************************************** * MyExit routine. Note it might call to CloseGraph without calling * * InitGraph(), or call MouseClose() without MouseInit() etc. and it is the * * responsibility of the individual modules to do nothing in these cases. * *****************************************************************************/ static void MyExit(int ExitCode) { #ifdef __MSDOS__ fprintf(stderr, "\nIrit2Nff: Core left %ldk.\n", coreleft() / 1024); #endif /* __MSDOS__ */ exit(ExitCode); }