Implement Three.js export.
parent
3c917ebac9
commit
4b02bf1e81
|
@ -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;
|
||||
|
|
341
src/export.cpp
341
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"
|
||||
"<!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.
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue