Implement Three.js export.

pull/4/head
William D. Jones 2015-08-31 15:33:57 -04:00 committed by whitequark
parent 3c917ebac9
commit 4b02bf1e81
3 changed files with 350 additions and 1 deletions

View File

@ -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;

View File

@ -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"
"<!DOCTYPE html>\n"
"<html lang=\"en\">\n"
" <head>\n"
" <meta charset=\"utf-8\"></meta>\n"
" <title>Three.js Solvespace Mesh</title>\n"
" <script src=\"http://threejs.org/build/three.min.js\"></script>\n"
" <script src=\"http://threejs.org/examples/js/controls/OrthographicTrackballControls.js\"></script>\n"
" <script src=\"%s.js\"></script>\n"
" </head>\n"
" <body>\n"
" <script>\n"
" window.solvespace = function(obj, params) {\n"
" var scene, edgeScene, camera, edgeCamera, renderer;\n"
" var geometry, controls, material, mesh, edges;\n"
" var width, height, edgeBias;\n"
" var directionalLightArray = [];\n"
"\n"
" if(typeof params === \"undefined\" || !(\"width\" in params)) {\n"
" width = window.innerWidth;\n"
" } else {\n"
" width = params.width;\n"
" }\n"
"\n"
" if(typeof params === \"undefined\" || !(\"height\" in params)) {\n"
" height = window.innerHeight;\n"
" } else {\n"
" height = params.height;\n"
" }\n"
" edgeBias = obj.bounds.edgeBias;\n"
"\n"
" domElement = init();\n"
" render();\n"
" return domElement;\n"
"\n"
" function init() {\n"
" scene = new THREE.Scene();\n"
" edgeScene = new THREE.Scene();\n"
"\n"
" var ratio = (width/height);\n"
" camera = new THREE.OrthographicCamera(-obj.bounds.x * ratio,\n"
" obj.bounds.x * ratio, obj.bounds.y, -obj.bounds.y, obj.bounds.near,\n"
" obj.bounds.far*10);\n"
" camera.position.z = obj.bounds.z*3;\n"
"\n"
" mesh = createMesh(obj);\n"
" scene.add(mesh);\n"
" edges = createEdges(obj);\n"
" edgeScene.add(edges);\n"
"\n"
" for(var i = 0; i < obj.lights.d.length; i++) {\n"
" var lightColor = new THREE.Color(obj.lights.d[i].intensity,\n"
" obj.lights.d[i].intensity, obj.lights.d[i].intensity);\n"
" var directionalLight = new THREE.DirectionalLight(lightColor, 1);\n"
" directionalLight.position.set(obj.lights.d[i].direction[0],\n"
" obj.lights.d[i].direction[1], obj.lights.d[i].direction[2]);\n"
" directionalLightArray.push(directionalLight);\n"
" scene.add(directionalLight);\n"
" }\n"
"\n"
" var lightColor = new THREE.Color(obj.lights.a, obj.lights.a, obj.lights.a);\n"
" var ambientLight = new THREE.AmbientLight(lightColor.getHex());\n"
" scene.add(ambientLight);\n"
"\n"
" renderer = new THREE.WebGLRenderer();\n"
" renderer.setPixelRatio(window.devicePixelRatio);\n"
" renderer.setSize(width, height);\n"
" renderer.autoClear = false;\n"
"\n"
" controls = new THREE.OrthographicTrackballControls(camera, renderer.domElement);\n"
" controls.screen.width = width;\n"
" controls.screen.height = height;\n"
" controls.radius = (width + height)/4;\n"
" controls.rotateSpeed = 2.0;\n"
" controls.zoomSpeed = 2.0;\n"
" controls.panSpeed = 1.0;\n"
" controls.staticMoving = true;\n"
" controls.addEventListener(\"change\", render);\n"
" controls.addEventListener(\"change\", lightUpdate);\n"
" controls.addEventListener(\"change\", setControlsCenter);\n"
"\n"
" animate();\n"
" return renderer.domElement;\n"
" }\n"
"\n"
" function animate() {\n"
" requestAnimationFrame(animate);\n"
" controls.update();\n"
" }\n"
"\n"
" function render() {\n"
" renderer.clear();\n"
" renderer.render(scene, camera);\n"
" var oldFar = camera.far\n"
" camera.far = camera.far + edgeBias;\n"
" camera.updateProjectionMatrix();\n"
" renderer.render(edgeScene, camera);\n"
" camera.far = oldFar;\n"
" camera.updateProjectionMatrix();\n"
" }\n"
"\n"
" function lightUpdate() {\n"
" var projRight = new THREE.Vector3();\n"
" var projZ = new THREE.Vector3();\n"
" var changeBasis = new THREE.Matrix3();\n"
"\n"
" // The original light positions were in camera space.\n"
" // Project them into standard space using camera's basis\n"
" // vectors (up, target, and their cross product).\n"
" projRight.copy(camera.up);\n"
" projZ.copy(camera.position).sub(controls.target).normalize();\n"
" projRight.cross(projZ).normalize();\n"
" changeBasis.set(projRight.x, camera.up.x, controls.target.x,\n"
" projRight.y, camera.up.y, controls.target.y,\n"
" projRight.z, camera.up.z, controls.target.z);\n"
"\n"
" for(var i = 0; i < obj.lights.d.length; i++) {\n"
" var newLightPos = changeBasis.applyToVector3Array(\n"
" [obj.lights.d[i].direction[0], obj.lights.d[i].direction[1],\n"
" obj.lights.d[i].direction[2]]);\n"
" directionalLightArray[i].position.set(newLightPos[0],\n"
" newLightPos[1], newLightPos[2]);\n"
" }\n"
" }\n"
"\n"
" function setControlsCenter() {\n"
" var rect = renderer.domElement.getBoundingClientRect()\n"
" controls.screen.left = rect.left + document.body.scrollLeft;\n"
" controls.screen.top = rect.top + document.body.scrollTop;\n"
" }\n"
"\n"
" function createMesh(mesh_obj) {\n"
" var geometry = new THREE.Geometry();\n"
" var materialIndex = 0, materialList = [];\n"
" var opacitiesSeen = {};\n"
"\n"
" for(var i = 0; i < mesh_obj.points.length; i++) {\n"
" geometry.vertices.push(new THREE.Vector3(mesh_obj.points[i][0],\n"
" mesh_obj.points[i][1], mesh_obj.points[i][2]));\n"
" }\n"
"\n"
" for(var i = 0; i < mesh_obj.faces.length; i++) {\n"
" var currOpacity = ((mesh_obj.colors[i] & 0xFF000000) >>> 24)/255.0;\n"
" if(opacitiesSeen[currOpacity] === undefined) {\n"
" opacitiesSeen[currOpacity] = materialIndex;\n"
" materialIndex++;\n"
" materialList.push(new THREE.MeshLambertMaterial(\n"
" {vertexColors: THREE.FaceColors, opacity: currOpacity,\n"
" transparent: true}));\n"
" }\n"
"\n"
" geometry.faces.push(new THREE.Face3(mesh_obj.faces[i][0],\n"
" mesh_obj.faces[i][1], mesh_obj.faces[i][2],\n"
" new THREE.Vector3(mesh_obj.normals[i][0],\n"
" mesh_obj.normals[i][1], mesh_obj.normals[i][2]),\n"
" new THREE.Color(mesh_obj.colors[i] & 0x00FFFFFF),\n"
" opacitiesSeen[currOpacity]));\n"
" }\n"
"\n"
" geometry.computeBoundingSphere();\n"
" return new THREE.Mesh(geometry, new THREE.MeshFaceMaterial(materialList));\n"
" }\n"
"\n"
" function createEdges(mesh_obj) {\n"
" var geometry = new THREE.Geometry();\n"
" var material = new THREE.LineBasicMaterial();\n"
"\n"
" for(var i = 0; i < mesh_obj.edges.length; i++) {\n"
" geometry.vertices.push(new THREE.Vector3(mesh_obj.edges[i][0][0],\n"
" mesh_obj.edges[i][0][1], mesh_obj.edges[i][0][2]),\n"
" new THREE.Vector3(mesh_obj.edges[i][1][0],\n"
" mesh_obj.edges[i][1][1], mesh_obj.edges[i][1][2]));\n"
" }\n"
"\n"
" return new THREE.Line(geometry, material, THREE.LinePieces);\n"
" }\n"
" };\n"
"\n"
" document.body.appendChild(solvespace(three_js_%s));\n"
" </script>\n"
" </body>\n"
"</html>\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.

View File

@ -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,