From 4b02bf1e812b43f1271b6d414eaec4180636645d Mon Sep 17 00:00:00 2001 From: "William D. Jones" Date: Mon, 31 Aug 2015 15:33:57 -0400 Subject: [PATCH] Implement Three.js export. --- src/dsc.h | 8 ++ src/export.cpp | 341 ++++++++++++++++++++++++++++++++++++++++++++++- src/solvespace.h | 2 + 3 files changed, 350 insertions(+), 1 deletion(-) diff --git a/src/dsc.h b/src/dsc.h index 3c527e1..c8b37a7 100644 --- a/src/dsc.h +++ b/src/dsc.h @@ -427,6 +427,14 @@ public: (uint32_t)((255 - alpha) << 24); } + uint32_t ToARGB32(void) const { + return + blue | + (uint32_t)(green << 8) | + (uint32_t)(red << 16) | + (uint32_t)(alpha << 24); + } + static RgbaColor From(int r, int g, int b, int a = 255) { RgbaColor c; c.red = (uint8_t)r; diff --git a/src/export.cpp b/src/export.cpp index cc458ba..222f5b7 100644 --- a/src/export.cpp +++ b/src/export.cpp @@ -590,9 +590,12 @@ void SolveSpaceUI::ExportMeshTo(const char *filename) { ExportMeshAsStlTo(f, m); } else if(StringEndsIn(filename, ".obj")) { ExportMeshAsObjTo(f, m); + } else if(StringEndsIn(filename, ".js")) { + SEdgeList *e = &(SK.GetGroup(SS.GW.activeGroup)->displayEdges); + ExportMeshAsThreeJsTo(f, filename, m, e); } else { Error("Can't identify output file type from file extension of " - "filename '%s'; try .stl, .obj.", filename); + "filename '%s'; try .stl, .obj, .js.", filename); } fclose(f); @@ -669,6 +672,342 @@ void SolveSpaceUI::ExportMeshAsObjTo(FILE *f, SMesh *sm) { spl.Clear(); } +//----------------------------------------------------------------------------- +// Export the mesh as a JavaScript script, which is compatible with Three.js. +//----------------------------------------------------------------------------- +void SolveSpaceUI::ExportMeshAsThreeJsTo(FILE *f, const char * filename, SMesh *sm, + SEdgeList *sel) +{ + SPointList spl; + ZERO(&spl); + STriangle *tr; + SEdge *e; + Vector bndl, bndh; + const char html[] = + "/* Autogenerated Three.js viewer for Solvespace Model (copy into another document):\n" + "\n" + "\n" + " \n" + " \n" + " Three.js Solvespace Mesh\n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + " \n" + "\n" + "*/\n\n"; + + // A default three.js viewer with OrthographicTrackballControls is + // generated as a comment preceding the data. + + // x bounds should be the range of x or y, whichever + // is larger, before aspect ratio correction is applied. + // y bounds should be the range of x or y, whichever is + // larger. No aspect ratio correction is applied. + // Near plane should be 1. + // Camera's z-position should be the range of z + 1 or the larger of + // the x or y bounds, whichever is larger. + // Far plane should be at least twice as much as the camera's + // z-position. + // Edge projection bias should be about 1/500 of the far plane's distance. + // Further corrections will be applied to the z-position and far plane in + // the default viewer, but the defaults are fine for a model which + // only rotates about the world origin. + + sm->GetBounding(&bndh, &bndl); + double largerBoundXY = max((bndh.x - bndl.x), (bndh.y - bndl.y)); + double largerBoundZ = max(largerBoundXY, (bndh.z - bndl.z + 1)); + + char *baseFilename = (char *) MemAlloc(strlen(filename)); + const char *lastSlash; + +#ifdef WIN32 + lastSlash = strrchr(filename, '\\'); +#else + lastSlash = strrchr(filename, '/'); +#endif + + if(!lastSlash) oops(); + lastSlash++; // Point one past last slash. + strcpy(baseFilename, lastSlash); // Copy the file w/ extension. + *strrchr(baseFilename, '.') = '\0'; // Strip extension. + + for(int i = 0; i < strlen(baseFilename); i++) { + if(!isalpha(baseFilename[i]) && + /* also permit UTF-8 */ !((unsigned char)baseFilename[i] >= 0x80)) + baseFilename[i] = '_'; + } + + fprintf(f, html, baseFilename, baseFilename); + fprintf(f, "var three_js_%s = {\n" + " bounds: {\n" + " x: %f, y: %f, near: %f, far: %f, z: %f, edgeBias: %f\n" + " },\n", + baseFilename, + largerBoundXY, + largerBoundXY, + 1.0, + largerBoundZ * 2, + largerBoundZ, + largerBoundZ / 250); + + MemFree(baseFilename); + + // Output lighting information. + fputs(" lights: {\n" + " d: [\n", f); + + // Directional. + int lightCount; + for(lightCount = 0; lightCount < 2; lightCount++) + { + fprintf(f, " {\n" + " intensity: %f, direction: [%f, %f, %f]\n" + " },\n", + SS.lightIntensity[lightCount], + SS.lightDir[lightCount].x, + SS.lightDir[lightCount].y, + SS.lightDir[lightCount].z); + } + + // Global Ambience. + fprintf(f, " ],\n" + " a: %f\n", SS.ambientIntensity); + + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + spl.IncrementTagFor(tr->a); + spl.IncrementTagFor(tr->b); + spl.IncrementTagFor(tr->c); + } + + // Output all the vertices. + SPoint *sp; + fputs(" },\n" + " points: [\n", f); + for(sp = spl.l.First(); sp; sp = spl.l.NextAfter(sp)) { + fprintf(f, " [%f, %f, %f],\n", + sp->p.x / SS.exportScale, + sp->p.y / SS.exportScale, + sp->p.z / SS.exportScale); + } + + fputs(" ],\n" + " faces: [\n", f); + // And now all the triangular faces, in terms of those vertices. + // This time we count from zero. + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + fprintf(f, " [%d, %d, %d],\n", + spl.IndexForPoint(tr->a), + spl.IndexForPoint(tr->b), + spl.IndexForPoint(tr->c)); + } + + fputs(" ],\n" + " colors: [\n", f); + // Output triangle colors. + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + fprintf(f, " 0x%x,\n", tr->meta.color.ToARGB32()); + } + + fputs(" ],\n" + " normals: [\n", f); + // Output face normals. + for(tr = sm->l.First(); tr; tr = sm->l.NextAfter(tr)) { + Vector currNormal = tr->Normal(); + fprintf(f, " [%f, %f, %f],\n", + currNormal.x, + currNormal.y, + currNormal.z); + } + + fputs(" ],\n" + " edges: [\n", f); + // Output edges. Assume user's model colors do not obscure white edges. + for(e = sel->l.First(); e; e = sel->l.NextAfter(e)) { + fprintf(f, " [[%f, %f, %f], [%f, %f, %f]],\n", + e->a.x, + e->a.y, + e->a.z, + e->b.x, + e->b.y, + e->b.z); + } + + fputs(" ]\n};\n", f); + spl.Clear(); +} + //----------------------------------------------------------------------------- // Export a view of the model as an image; we just take a screenshot, by // rendering the view in the usual way and then copying the pixels. diff --git a/src/solvespace.h b/src/solvespace.h index b4a9108..b13a162 100644 --- a/src/solvespace.h +++ b/src/solvespace.h @@ -170,6 +170,7 @@ int LoadAutosaveYesNo(void); #define MESH_PATTERN \ PAT1("STL Mesh", "stl") \ PAT1("Wavefront OBJ Mesh", "obj") \ + PAT1("Three.js-compatible JavaScript Mesh", "js") \ ENDPAT #define MESH_EXT "stl" // NURBS surfaces @@ -840,6 +841,7 @@ public: void ExportMeshTo(const char *file); void ExportMeshAsStlTo(FILE *f, SMesh *sm); void ExportMeshAsObjTo(FILE *f, SMesh *sm); + void ExportMeshAsThreeJsTo(FILE *f, const char * filename, SMesh *sm, SEdgeList *sel); void ExportViewOrWireframeTo(const char *file, bool wireframe); void ExportSectionTo(const char *file); void ExportWireframeCurves(SEdgeList *sel, SBezierList *sbl,