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,