/*
## License
Copyright (c) 2016 yoderjen (yoderjen@gmail.com)
Copyright (c) 2014 bebbi (elghatta@gmail.com)
Copyright (c) 2013 Eduard Bespalov (edwbes@gmail.com)
Copyright (c) 2012 Joost Nieuwenhuijse (joost@newhouse.nl)
Copyright (c) 2011 Evan Wallace (http://evanw.github.com/csg.js/)
Copyright (c) 2012 Alexandre Girard (https://github.com/alx)
All code released under MIT license
*/
// import the required modules if necessary
if(typeof module !== 'undefined') { // used via nodejs
CSG = require(lib+'csg.js').CSG;
CAG = require(lib+'csg.js').CAG;
Blob = require(lib+'Blob.js').Blob;
}
////////////////////////////////////////////
// X3D Export
////////////////////////////////////////////
(function(module) {
CSG.prototype.toX3D = function() {
// materialPolygonLists
// key: a color string (e.g. "0 1 1" for yellow)
// value: an array of strings specifying polygons of this color
// (as space-separated indices into vertexCoords)
var materialPolygonLists = {},
// list of coordinates (as "x y z" strings)
vertexCoords = [],
// map to look up the index in vertexCoords of a given vertex
vertexTagToCoordIndexMap = {};
this.polygons.map(function(p) {
var red = 0,
green = 0,
blue = 1; // default color is blue
if (p.shared && p.shared.color) {
red = p.shared.color[0];
green = p.shared.color[1];
blue = p.shared.color[2];
}
var polygonVertexIndices = [],
numvertices = p.vertices.length,
vertex;
for (var i = 0; i < numvertices; i++) {
vertex = p.vertices[i];
if (!(vertex.getTag() in vertexTagToCoordIndexMap)) {
vertexCoords.push(vertex.pos._x.toString() + " " +
vertex.pos._y.toString() + " " +
vertex.pos._z.toString()
);
vertexTagToCoordIndexMap[vertex.getTag()] = vertexCoords.length - 1;
}
polygonVertexIndices.push(vertexTagToCoordIndexMap[vertex.getTag()]);
}
var polygonString = polygonVertexIndices.join(" ");
var colorString = red.toString() + " " + green.toString() + " " + blue.toString();
if (!(colorString in materialPolygonLists)) {
materialPolygonLists[colorString] = [];
}
// add this polygonString to the list of colorString-colored polygons
materialPolygonLists[colorString].push(polygonString);
});
// create output document
var docType = document.implementation.createDocumentType("X3D",
"ISO//Web3D//DTD X3D 3.1//EN", "http://www.web3d.org/specifications/x3d-3.1.dtd");
var exportDoc = document.implementation.createDocument(null, "X3D", docType);
exportDoc.insertBefore(
exportDoc.createProcessingInstruction('xml', 'version="1.0" encoding="UTF-8"'),
exportDoc.doctype);
var exportRoot = exportDoc.getElementsByTagName("X3D")[0];
exportRoot.setAttribute("profile", "Interchange");
exportRoot.setAttribute("version", "3.1");
exportRoot.setAttribute("xsd:noNamespaceSchemaLocation", "http://www.web3d.org/specifications/x3d-3.1.xsd");
exportRoot.setAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema-instance");
var exportScene = exportDoc.createElement("Scene");
exportRoot.appendChild(exportScene);
/*
For each color, create a shape made of an appropriately colored
material which contains all polygons that are this color.
The first shape will contain the definition of all vertices,
(), which will be referenced by
subsequent shapes.
*/
var coordsMeshDefined = false;
for (var colorString in materialPolygonLists) {
var polygonList = materialPolygonLists[colorString];
var shape = exportDoc.createElement("Shape");
exportScene.appendChild(shape);
var appearance = exportDoc.createElement("Appearance");
shape.appendChild(appearance);
var material = exportDoc.createElement("Material");
appearance.appendChild(material);
material.setAttribute("diffuseColor", colorString);
material.setAttribute("ambientIntensity", "1.0");
var ifs = exportDoc.createElement("IndexedFaceSet");
shape.appendChild(ifs);
ifs.setAttribute("solid", "true");
ifs.setAttribute("coordIndex", polygonList.join(" -1 ") + " -1");
var coordinate = exportDoc.createElement("Coordinate");
ifs.appendChild(coordinate);
if (coordsMeshDefined) {
coordinate.setAttribute("USE", "coords_mesh");
} else {
coordinate.setAttribute("DEF", "coords_mesh");
coordinate.setAttribute("point", vertexCoords.join(" "));
coordsMeshDefined = true;
}
}
var x3dstring = (new XMLSerializer()).serializeToString(exportDoc);
return new Blob([x3dstring], {
type: "model/x3d+xml"
});
};
////////////////////////////////////////////
// STL Binary Export
////////////////////////////////////////////
// see http://en.wikipedia.org/wiki/STL_%28file_format%29#Binary_STL
CSG.prototype.toStlBinary = function() {
// first check if the host is little-endian:
var buffer = new ArrayBuffer(4);
var int32buffer = new Int32Array(buffer, 0, 1);
var int8buffer = new Int8Array(buffer, 0, 4);
int32buffer[0] = 0x11223344;
if (int8buffer[0] != 0x44) {
throw new Error("Binary STL output is currently only supported on little-endian (Intel) processors");
}
var numtriangles = 0;
this.polygons.map(function(p) {
var numvertices = p.vertices.length;
var thisnumtriangles = (numvertices >= 3) ? numvertices - 2 : 0;
numtriangles += thisnumtriangles;
});
var headerarray = new Uint8Array(80);
for (var i = 0; i < 80; i++) {
headerarray[i] = 65;
}
var ar1 = new Uint32Array(1);
ar1[0] = numtriangles;
// write the triangles to allTrianglesBuffer:
var allTrianglesBuffer = new ArrayBuffer(50 * numtriangles);
var allTrianglesBufferAsInt8 = new Int8Array(allTrianglesBuffer);
// a tricky problem is that a Float32Array must be aligned at 4-byte boundaries (at least in certain browsers)
// while each triangle takes 50 bytes. Therefore we write each triangle to a temporary buffer, and copy that
// into allTrianglesBuffer:
var triangleBuffer = new ArrayBuffer(50);
var triangleBufferAsInt8 = new Int8Array(triangleBuffer);
// each triangle consists of 12 floats:
var triangleFloat32array = new Float32Array(triangleBuffer, 0, 12);
// and one uint16:
var triangleUint16array = new Uint16Array(triangleBuffer, 48, 1);
var byteoffset = 0;
this.polygons.map(function(p) {
var numvertices = p.vertices.length;
for (var i = 0; i < numvertices - 2; i++) {
var normal = p.plane.normal;
triangleFloat32array[0] = normal._x;
triangleFloat32array[1] = normal._y;
triangleFloat32array[2] = normal._z;
var arindex = 3;
for (var v = 0; v < 3; v++) {
var vv = v + ((v > 0) ? i : 0);
var vertexpos = p.vertices[vv].pos;
triangleFloat32array[arindex++] = vertexpos._x;
triangleFloat32array[arindex++] = vertexpos._y;
triangleFloat32array[arindex++] = vertexpos._z;
}
triangleUint16array[0] = 0;
// copy the triangle into allTrianglesBuffer:
allTrianglesBufferAsInt8.set(triangleBufferAsInt8, byteoffset);
byteoffset += 50;
}
});
return new Blob([headerarray.buffer, ar1.buffer, allTrianglesBuffer], {
type: "application/sla"
});
};
////////////////////////////////////////////
// STL String Export
////////////////////////////////////////////
CSG.prototype.toStlString = function() {
var result = "solid csg.js\n";
this.polygons.map(function(p) {
result += p.toStlString();
});
result += "endsolid csg.js\n";
return new Blob([result], {
type: "application/sla"
});
};
CSG.Vector3D.prototype.toStlString = function() {
return this._x + " " + this._y + " " + this._z;
};
CSG.Vertex.prototype.toStlString = function() {
return "vertex " + this.pos.toStlString() + "\n";
};
CSG.Polygon.prototype.toStlString = function() {
var result = "";
if (this.vertices.length >= 3) // should be!
{
// STL requires triangular polygons. If our polygon has more vertices, create
// multiple triangles:
var firstVertexStl = this.vertices[0].toStlString();
for (var i = 0; i < this.vertices.length - 2; i++) {
result += "facet normal " + this.plane.normal.toStlString() + "\nouter loop\n";
result += firstVertexStl;
result += this.vertices[i + 1].toStlString();
result += this.vertices[i + 2].toStlString();
result += "endloop\nendfacet\n";
}
}
return result;
};
//
// OBJ (Wavefront) ASCII Export
//
CSG.prototype.toObj = function() {
var result = "# OBJ file\n";
var vertices = {};
var vertexList = "";
var faces = {};
var count = 1;
var red = 0;
var green = 0;
var blue = 0;
var alpha = 1;
var p = null;
var vstring = "";
var colorString = "";
// I want to put different colored vertices into different groups.
// group names (and keys) will be a stringy version of their color.
// I don't want to list vertices more than once. Hash them and be careful of the count.
for (var i = 0; i < this.polygons.length; i++) {
p = this.polygons[i];
if (p.shared && p.shared.color) {
red = p.shared.color[0] * 255;
green = p.shared.color[1] * 255;
blue = p.shared.color[2] * 255;
if (p.shared.color[3])
alpha = p.shared.color[3];
}
else {
red = 0;
green = 0;
blue = 0;
alpha = 0;
}
colorString = red.toFixed(0) + "-" + green.toFixed(0) + "-" + blue.toFixed(0) + "-" + alpha.toFixed(0);
if (!faces[colorString])
faces[colorString] = "";
faces[colorString] += 'f ';
// now deal with the vertices
for (var j = 0; j < this.polygons[i].vertices.length; j++) {
var v = p.vertices[j];
vstring = 'v ' + v.pos._x + ' ' + v.pos._y + ' ' + v.pos._z + '\n';
if (vertices[vstring]) {
// this vertex was already added to the list. We can get its index in vertexList from vertices.
faces[colorString] += vertices[vstring] + ' ';
}
else {
// this is a new vertex. Add it to the vertex list, increment count, and then add to the face.
// list it as "added at this index" in vertices.
vertices[vstring] = count.toString();
vertexList += vstring;
faces[colorString] += count.toString() + ' ';
count++;
}
}
faces[colorString] += '\n';
}
result += vertexList;
for (i in faces) {
result += 'g ' + i + '\n';
result += faces[i];
}
return result;
}
////////////////////////////////////////////
// DXF Export
////////////////////////////////////////////
CAG.PathsToDxf = function(paths) {
var str = "999\nDXF generated by OpenJsCad\n";
str += " 0\nSECTION\n 2\nHEADER\n";
str += " 0\nENDSEC\n";
str += " 0\nSECTION\n 2\nTABLES\n";
str += " 0\nTABLE\n 2\nLTYPE\n 70\n1\n";
str += " 0\nLTYPE\n 2\nCONTINUOUS\n 3\nSolid Line\n 72\n65\n 73\n0\n 40\n0.0\n";
str += " 0\nENDTAB\n";
str += " 0\nTABLE\n 2\nLAYER\n 70\n1\n";
str += " 0\nLAYER\n 2\nOpenJsCad\n 62\n7\n 6\ncontinuous\n";
str += " 0\nENDTAB\n";
str += " 0\nTABLE\n 2\nSTYLE\n 70\n0\n 0\nENDTAB\n";
str += " 0\nTABLE\n 2\nVIEW\n 70\n0\n 0\nENDTAB\n";
str += " 0\nENDSEC\n";
str += " 0\nSECTION\n 2\nBLOCKS\n";
str += " 0\nENDSEC\n";
str += " 0\nSECTION\n 2\nENTITIES\n";
paths.map(function(path) {
var numpoints_closed = path.points.length + (path.closed ? 1 : 0);
str += " 0\nLWPOLYLINE\n 8\nOpenJsCad\n 90\n" + numpoints_closed + "\n 70\n" + (path.closed ? 1 : 0) + "\n";
for (var pointindex = 0; pointindex < numpoints_closed; pointindex++) {
var pointindexwrapped = pointindex;
if (pointindexwrapped >= path.points.length) pointindexwrapped -= path.points.length;
var point = path.points[pointindexwrapped];
str += " 10\n" + point.x + "\n 20\n" + point.y + "\n 30\n0.0\n";
}
});
str += " 0\nENDSEC\n 0\nEOF\n";
return new Blob([str], {
type: "application/dxf"
});
};
CAG.prototype.toDxf = function() {
var paths = this.getOutlinePaths();
return CAG.PathsToDxf(paths);
};
////////////////////////////////////////////
// AMF Export
////////////////////////////////////////////
CSG.prototype.toAMFString = function(m) {
var result = "\n\n";
for(var k in m) {
result += ""+m[k]+"\n";
}
result += "\n";
result += "\n";
return new Blob([result], {
type: "application/amf+xml"
});
};
CSG.Vector3D.prototype.toAMFString = function() {
return "" + this._x + "" + this._y + "" + this._z + "";
};
CSG.Vertex.prototype.toAMFString = function() {
return "" + this.pos.toAMFString() + "\n";
};
// re-export CSG and CAG with the extended prototypes
module.CSG = CSG;
module.CAG = CAG;
})(this);