Permalink
Cannot retrieve contributors at this time
Fetching contributors…

// namespace | |
var SLAcer = SLAcer || {}; | |
;(function() { | |
// global settings | |
var globalSettings = { | |
color: 0xffff00 | |
}; | |
// ------------------------------------------------------------------------- | |
function Point(p) { | |
this.x = p.x; | |
this.y = p.y; | |
this.s = p.x + ':' + p.y; | |
} | |
function Line(p1, p2) { | |
this.p1 = new Point(p1); | |
this.p2 = new Point(p2); | |
} | |
function isSameValue(v1, v2) { | |
return Math.abs(v1 - v2) <= Number.EPSILON; | |
} | |
function isSamePoint(p1, p2) { | |
return isSameValue(p1.x, p2.x) && isSameValue(p1.y, p2.y); | |
} | |
function linesToPolygons(lines) { | |
var polygons = []; | |
var polygon = []; | |
var firstLine, lastPoint, i, line; | |
function getNextPoint() { | |
var found = false; | |
for (i = 0; i < lines.length; i++) { | |
line = lines[i]; | |
if (isSamePoint(lastPoint, line.p1)) { | |
lines.splice(i, 1); | |
if (isSamePoint(firstLine.p1, line.p2)) { | |
//console.log('closed loop'); | |
break; | |
} | |
polygon.push(line.p2); | |
lastPoint = line.p2; | |
found = true; | |
} | |
else if (isSamePoint(lastPoint, line.p2)) { | |
lines.splice(i, 1); | |
if (isSamePoint(firstLine.p1, line.p1)) { | |
//console.log('closed loop'); | |
break; | |
} | |
polygon.push(line.p1); | |
lastPoint = line.p1; | |
found = true; | |
} | |
} | |
return found; | |
} | |
while (lines.length) { | |
firstLine = lines.shift(); | |
lastPoint = firstLine.p2; | |
polygon.push(firstLine.p1); | |
polygon.push(firstLine.p2); | |
while (getNextPoint()) {} | |
polygons.push(polygon); | |
polygon = []; | |
} | |
return polygons; | |
} | |
function pointInPolygon(point, polygon) { | |
// ray-casting algorithm based on | |
// https://github.com/substack/point-in-polygon/blob/master/index.js | |
// http://www.ecse.rpi.edu/Homepages/wrf/Research/Short_Notes/pnpoly.html | |
var inside = false; | |
var il, j, pi, pj, intersect; | |
for (i = 0, il = polygon.length, j = il - 1; i < il; j = i++) { | |
pi = polygon[i]; | |
pj = polygon[j]; | |
(((pi.y > point.y) != (pj.y > point.y)) | |
&& (point.x < (pj.x - pi.x) * (point.y - pi.y) / (pj.y - pi.y) + pi.x)) | |
&& (inside = !inside); | |
} | |
return inside; | |
}; | |
function makeNodes(polygons) { | |
// single polygon... | |
if (polygons.length == 1) { | |
return { 0: { parents: [], isHole: false } }; | |
} | |
// nodes collection | |
var nodes = {}; | |
// variables | |
var i, il, point, y, yl; | |
// for each polygon extract parents and childs polygons | |
for (i = 0, il = polygons.length; i < il; i++) { | |
// only check for first point in polygon | |
point = polygons[i][0]; | |
// not enough faces ( !!! investigation required !!!) | |
if (polygons[i].length < 3) { | |
//console.log('--------------------'); | |
//console.log(il, i, polygons[i]); | |
continue; | |
} | |
// for each polygons | |
for (y = 0, yl = il; y < yl; y++) { | |
// do not check self intersection | |
if (i == y) continue; | |
// create default node | |
nodes[i] || (nodes[i] = { parents: [], isHole: false }); | |
nodes[y] || (nodes[y] = { parents: [], isHole: false }); | |
// check if point in poylgon | |
if (pointInPolygon(point, polygons[y])) { | |
// push parent and child | |
nodes[i].parents.push(y); | |
// odd parents number ==> hole | |
nodes[i].isHole = !! (nodes[i].parents.length % 2); | |
nodes[y].isHole = !! (nodes[y].parents.length % 2); | |
} | |
} | |
} | |
// return nodes collection | |
return nodes; | |
} | |
function polygonsToShapes(polygons) { | |
// shapes collection | |
var shapes = []; | |
// make the nodes collection | |
var nodes = makeNodes(polygons); | |
//console.log('nodes:', nodes); | |
// variables | |
var key, node, i, il, parentKey; | |
// make base collection | |
for (key in nodes) { | |
node = nodes[key]; | |
if (! node.isHole) { | |
shapes[key] = new THREE.Shape(polygons[key]); | |
} | |
} | |
// push holes | |
for (key in nodes) { | |
node = nodes[key]; | |
if (node.isHole) { | |
for (i = 0, il = node.parents.length; i < il; i++) { | |
parentKey = node.parents[i]; | |
if ((il - 1) == nodes[parentKey].parents.length) { | |
shapes[parentKey].holes.push(new THREE.Path(polygons[key])); | |
} | |
} | |
} | |
} | |
// return shapes collection | |
return shapes; | |
} | |
// ------------------------------------------------------------------------- | |
// Constructor | |
function Slicer(settings) { | |
// settings | |
this.settings = _.defaults({}, settings || {}, Slicer.globalSettings); | |
// faces collection | |
this.faces = []; | |
// position | |
this.zHeight = 0; | |
this.zOffset = 0; | |
// plane | |
this.mesh = null; | |
this.plane = null; | |
this.slice = null; | |
} | |
// ------------------------------------------------------------------------- | |
Slicer.prototype.loadMesh = function(mesh) { | |
// mesh | |
this.mesh = mesh; | |
// slice | |
this.slice = mesh.clone(); | |
// bounding box | |
var box = mesh.geometry.boundingBox.clone(); | |
// mesh size | |
var size = box.size(); | |
// z height | |
this.zHeight = size.z; | |
this.zOffset = box.min.z; | |
// min/max faces (z) | |
this.facesMinMax = []; | |
var i, length, face, v1, v2, v3; | |
var geometry = this.slice.geometry; | |
for (i = 0, length = geometry.faces.length; i < length; i++) { | |
face = geometry.faces[i]; | |
v1 = geometry.vertices[face.a]; | |
v2 = geometry.vertices[face.b]; | |
v3 = geometry.vertices[face.c]; | |
this.facesMinMax.push({ | |
min: Math.min(v1.z, v2.z, v3.z), | |
max: Math.max(v1.z, v2.z, v3.z) | |
}); | |
} | |
// plane | |
this.plane = new SLAcer.Mesh( | |
new THREE.PlaneGeometry(size.x, size.y, 1), | |
new THREE.MeshBasicMaterial({ | |
color: this.settings.color, side: THREE.DoubleSide | |
}) | |
); | |
}; | |
Slicer.prototype.getFaces = function(zPosition) { | |
var time = Date.now(); | |
zPosition += this.zOffset; | |
var source = this.slice.geometry; | |
var geometry = new THREE.Geometry(); | |
var plane = new THREE.Plane(new THREE.Vector3(0, 0, 1), -zPosition); | |
var i, length, minMax, face, vertices, v1, v2, v3, index, normal; | |
var lines = []; | |
var line, top, bot; | |
function addLine(p1, p2) { | |
lines.push(new Line(p1, p2)); | |
} | |
for (i = 0, length = source.faces.length; i < length; i++) { | |
minMax = this.facesMinMax[i]; | |
if (minMax.min <= zPosition && minMax.max >= zPosition) { | |
face = source.faces[i]; | |
vertices = source.vertices; | |
v1 = vertices[face.a].clone(); | |
v2 = vertices[face.b].clone(); | |
v3 = vertices[face.c].clone(); | |
geometry.vertices.push(v1, v2, v3); | |
index = geometry.vertices.length; | |
normal = face.normal.clone(); | |
geometry.faces.push(new THREE.Face3( | |
index-3, index-2, index-1, normal | |
)); | |
// slice... | |
t1 = isSameValue(v1.z, zPosition); | |
t2 = isSameValue(v2.z, zPosition); | |
t3 = isSameValue(v3.z, zPosition); | |
touch = 0; | |
t1 && touch++; | |
t2 && touch++; | |
t3 && touch++; | |
// all points on plane | |
if (touch == 3) { | |
// skip since is shared with two points case | |
continue; | |
} | |
// two points on plane | |
if (touch == 2) { | |
if (t1 && t2 && !t3) addLine(v1, v2); | |
else if (!t1 && t2 && t3) addLine(v2, v3); | |
else addLine(v3, v1); | |
continue; | |
} | |
// one point on plane | |
if (touch == 1) { | |
// test if faces intersect the plane | |
if (t1 && ((v2.z > zPosition && v3.z < zPosition) || (v2.z < zPosition && v3.z > zPosition))) { | |
addLine(v1, plane.intersectLine(new THREE.Line3(v2, v3))); | |
} | |
else if (t2 && ((v3.z > zPosition && v1.z < zPosition) || (v3.z < zPosition && v1.z > zPosition))) { | |
addLine(v2, plane.intersectLine(new THREE.Line3(v3, v1))); | |
} | |
else if (t3 && ((v1.z > zPosition && v2.z < zPosition) || (v1.z < zPosition && v2.z > zPosition))) { | |
addLine(v3, plane.intersectLine(new THREE.Line3(v1, v2))); | |
} | |
// no intersection! | |
// skip since is shared with two points case | |
continue; | |
} | |
// no points on plane (need intersection) | |
if (touch == 0) { | |
top = []; | |
bot = []; | |
v1.z > zPosition && top.push(v1) || bot.push(v1); | |
v2.z > zPosition && top.push(v2) || bot.push(v2); | |
v3.z > zPosition && top.push(v3) || bot.push(v3); | |
if (top.length == 1) { | |
addLine( | |
plane.intersectLine(new THREE.Line3(top[0], bot[0])), | |
plane.intersectLine(new THREE.Line3(top[0], bot[1])) | |
); | |
} | |
else { | |
addLine( | |
plane.intersectLine(new THREE.Line3(top[0], bot[0])), | |
plane.intersectLine(new THREE.Line3(top[1], bot[0])) | |
); | |
} | |
} | |
} | |
} | |
var polygons = linesToPolygons(lines); | |
var shapes = polygonsToShapes(polygons); | |
var meshes = []; | |
for (key in shapes) { | |
try { | |
var color = this.settings.color; | |
//var color = ((1<<24)*Math.random()|0); | |
var geo = new THREE.ShapeGeometry(shapes[key]); | |
if (!geo.faces.length || !geo.vertices.length) { | |
delete shapes[key]; | |
continue; | |
} | |
meshes.push(new THREE.Mesh( | |
geo, | |
new THREE.MeshBasicMaterial({ | |
color: color, side: THREE.DoubleSide | |
}) | |
)); | |
} | |
catch(e) { | |
console.error(e); | |
console.log(shapes[key]); | |
} | |
} | |
// remove empty shapes... | |
shapes = shapes.filter(function(n){ return n != undefined }); | |
return { | |
time : Date.now() - time, | |
geometry: geometry, | |
polygons: polygons, | |
shapes : shapes, | |
meshes : meshes | |
}; | |
}; | |
// ------------------------------------------------------------------------- | |
// global settings | |
Slicer.globalSettings = globalSettings; | |
// export module | |
SLAcer.Slicer = Slicer; | |
})(); |