holes-in
Version:
Generates a 3D mesh from a 2D outer path and 2D inner paths
234 lines (201 loc) • 9.59 kB
JavaScript
;
const pathHelper = require("./path-helper.js");
const geomHelper = require("./geom-helper.js");
const cdt2dHelper = require("./cdt2d-helper.js");
const uvHelper = require("./uv-helper.js");
const constants = require("./constants.js");
const extruder = {
/**
* returns a mesh from an outer shape and holes
*/
getGeometry(outerShape, holes, options = {}) {
options = Object.assign(extruder.getDefaultOptions(), options);
extruder.convertInputToHolesInConvention(outerShape, holes);
extruder.generateDebugLink(outerShape, holes, options)
// get the topology 2D paths by depth
const data = extruder.getDataByDepth(outerShape, holes);
const outerPathsByDepth = data.outerPathsByDepth;
const innerPathsByDepth = data.innerPathsByDepth;
const horizontalPathsByDepth = data.horizontalPathsByDepth;
if (options.doNotBuild) {
pathHelper.scaleUpPaths(options.doNotBuild);
extruder.markAsForbidden(outerPathsByDepth, options.doNotBuild);
extruder.markAsForbidden(innerPathsByDepth, options.doNotBuild);
}
uvHelper.mapVertical(outerPathsByDepth, outerShape, options);
const res = {};
if (options.frontMesh) {
res.frontMesh = extruder.getHorizontalGeom(horizontalPathsByDepth,innerPathsByDepth, [0], 0, !options.doNotInvertFrontNormal);
uvHelper.mapHorizontal(innerPathsByDepth, outerShape, res.frontMesh, options);
}
if (options.backMesh) {
res.backMesh = extruder.getHorizontalGeom(horizontalPathsByDepth, innerPathsByDepth, [horizontalPathsByDepth.length - 1], 0, options.invertBackNormal);
uvHelper.mapHorizontal(innerPathsByDepth, outerShape, res.backMesh, options);
}
if (options.inMesh) {
uvHelper.mapVertical(innerPathsByDepth, outerShape, options);
res.inMesh = extruder.getVerticalGeom(innerPathsByDepth, 0, true, options.mergeVerticalGeometries);
}
if (options.horizontalMesh) {
const indexes = [];
for (let i = 1; i < horizontalPathsByDepth.length - 1; i++) {
indexes.push(i);
}
const meshHor = extruder.getHorizontalGeom(horizontalPathsByDepth, innerPathsByDepth, indexes, 0);
if (meshHor) {
uvHelper.mapHorizontal(innerPathsByDepth, outerShape, meshHor, options);
}
res.horizontalMesh = meshHor;
}
if (options.outMesh) {
const outMesh = extruder.getVerticalGeom(outerPathsByDepth, 0, true, options.mergeVerticalGeometries);
res.outMesh = outMesh;
}
return res;
},
getVerticalGeom(innerPathsByDepth, offset = 0, invertNormal = false, mergeMeshes = true) {
const geom = [];
for (let indexDepth = innerPathsByDepth.length - 1; indexDepth > 0; indexDepth--) {
const pathsAtDepth = innerPathsByDepth[indexDepth].paths;
// for each point at each path at each depth we look for the corresponding point into the upper paths:
for (let i = 0; i < pathsAtDepth.length; i++) {
const path = pathsAtDepth[i];
for (let idxPtDwn = 0; idxPtDwn < path.length; idxPtDwn++) {
const idxNPtDwn = (idxPtDwn + 1) % path.length;
if(path[idxPtDwn]._holesInForbidden) continue;
const currgeom = geomHelper.getOneVerticalGeom(idxPtDwn, idxNPtDwn, +indexDepth, path,
innerPathsByDepth,0, invertNormal);
if (!currgeom) {
continue;
}
geom.push(currgeom);
}
}
}
if(mergeMeshes) {
return geomHelper.mergeMeshes(geom);
}
return geom;
},
/**
* Returns the geometry of the inner horizontal facess
*/
getHorizontalGeom(horizontalPathsByDepth, innerPathsByDepth, indexes, offset = 0, invertNormal = true) {
const horrGeom = [];
for (let i = 0; i < indexes.length; i++) {
const innerPaths = innerPathsByDepth[indexes[i]].paths;
const paths = horizontalPathsByDepth[indexes[i]].paths;
const triangles = cdt2dHelper.computeTriangulation(paths);
triangles.depth = horizontalPathsByDepth[indexes[i]].depth;
horrGeom.push(geomHelper.getHorizontalGeom(triangles, 0, invertNormal));
}
// get points, normal and faces from it:
return geomHelper.mergeMeshes(horrGeom);
},
getDataByDepth(outerShape,holes) {
// sets all depths deeper than outerDepth or equals to 0 to outerDepth:
holes.forEach(elt => {
(elt.depth >= outerShape.depth || elt.depth === 0) ? elt.depth = outerShape.depth + 1 : elt.depth = elt.depth; // eslint-disable-line
});
// get all depths:
let depths = new Set();
for (let i = 0; i < holes.length; i++) {
if (holes[i].depth < outerShape.depth) { depths.add(holes[i].depth); }
}
depths.add(0);
depths.add(outerShape.depth);
depths = Array.from(depths);
depths.sort(
function (a, b) {
return a - b;
});
// filter:
holes = holes.filter(hole => hole.path !== undefined);
holes.forEach(hole => pathHelper.scaleUpPath(hole.path));
pathHelper.scaleUpPath(outerShape.path);
const outerPaths = [];
const innerPaths = [];
const horriPaths = [];
depths.forEach((depth,i )=> {
let keep = holes.filter(hole => hole.depth >= depth).map(hole => hole.path);
const diff = pathHelper.getDiffOfPaths([outerShape.path], keep, {polyTree: false});
const outerPathAtDepth = diff.filter(path => pathHelper.getDirectionPath(path) > 0);
const innerPathAtDepth = diff.filter(path => pathHelper.getDirectionPath(path) < 0);
outerPaths.push({paths: pathHelper.cleanPaths(outerPathAtDepth, 20), depth});
innerPaths.push({paths: pathHelper.cleanPaths(innerPathAtDepth, 20), depth});
let horr;
if(i===0){
horr = outerPathAtDepth.concat(innerPathAtDepth);
}else{
keep = holes.filter(hole => hole.depth > depth).map(hole => hole.path);
const stop = pathHelper.getUnionOfPaths(holes.filter(hole => hole.depth === depth).map(hole => hole.path));
horr = [JSON.parse(JSON.stringify(outerShape.path))];
if(stop.length > 0) {
horr = pathHelper.getInterOfPaths(horr, stop,{ pathPreProcess: false});
}
horr = pathHelper.getDiffOfPaths(horr, keep, {pathPreProcess: false});
horr = pathHelper.cleanPaths(horr, 20);
}
horriPaths.push({paths:horr, depth });
});
const allHorrizontals = horriPaths.map(elt => elt.paths);
horriPaths[horriPaths.length-1].paths = outerPaths[outerPaths.length-1].paths.concat(innerPaths[innerPaths.length-1].paths);
return { outerPathsByDepth: outerPaths,
innerPathsByDepth: innerPaths,
horizontalPathsByDepth: horriPaths,
depthsCount: outerPaths.length,
};
},
// mark all points that bbelong to one of the edges as forbidden
markAsForbidden(pathsByDepth, edges) {
for (let j = 0; j < edges.length; j++) {
const edge = edges[j];
for (let i = pathsByDepth.length - 1; i >= 0; i--) {
const paths = pathsByDepth[i].paths;
for (let k = 0; k < paths.length; k++) {
const path = paths[k];
const toMark = pathHelper.getPointsOnEdge(path, edge);
toMark.map(pt => pt._holesInForbidden = true);
}
}
}
},
getDefaultOptions() {
return {
inMesh: true,
outMesh: true,
frontMesh: true,
backMesh: true,
horizontalMesh: true,
mergeVerticalGeometries: true,
doNotBuild: [],
debug:false,
doNotInvertFrontNormal: false,
invertBackNormal: false,
};
},
generateDebugLink(outerShape, holes, options){
if(options.debug){
try{
const pako = require("pako");
const data64 = pako.deflate(JSON.stringify({ holes, outerShape, doNotBuild: options.doNotBuild }));
const urlParam = `data=${encodeURIComponent(data64)}`;
console.info(`Holes in debug: https://wanadev.github.io/holes-in/debugPage.html?${urlParam}`);
}catch(error) {
console.warn("error on holes-in generate debug link. You may need to install pako", error)
}
}
},
convertInputToHolesInConvention(outerShape, holes){
outerShape.path = extruder.convertPathToHolesInConvention(outerShape.path);
holes.forEach(hole => hole.path = extruder.convertPathToHolesInConvention(hole.path));
},
// checks if there is X,Y coordinates, if not uses x,y
convertPathToHolesInConvention(path){
return path.map(point => ({
X: point.X !== undefined ? point.X: point.x,
Y: point.Y !== undefined ? point.Y: point.y,
}));
},
};
module.exports = extruder;