mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-09 18:02:50 +01:00
1016 lines
29 KiB
JavaScript
1016 lines
29 KiB
JavaScript
|
|
TCAD.utils = {};
|
|
|
|
TCAD.utils.createSquare = function(width) {
|
|
|
|
width /= 2;
|
|
|
|
return [
|
|
new TCAD.Vector(-width, -width, 0),
|
|
new TCAD.Vector( width, -width, 0),
|
|
new TCAD.Vector( width, width, 0),
|
|
new TCAD.Vector(-width, width, 0)
|
|
];
|
|
};
|
|
|
|
TCAD.utils.csgVec = function(v) {
|
|
return new CSG.Vector3D(v.x, v.y, v.z);
|
|
};
|
|
|
|
TCAD.utils.vec = function(v) {
|
|
return new TCAD.Vector(v.x, v.y, v.z);
|
|
};
|
|
|
|
TCAD.utils.createBox = function(width) {
|
|
var square = TCAD.utils.createSquare(width);
|
|
var rot = TCAD.math.rotateMatrix(3/4, TCAD.math.AXIS.Z, TCAD.math.ORIGIN);
|
|
square.forEach(function(v) { rot._apply(v) } );
|
|
var normal = TCAD.geom.normalOfCCWSeq(square);
|
|
return TCAD.geom.extrude(square, normal, normal.multiply(width), 1);
|
|
};
|
|
|
|
TCAD.utils.createCSGBox = function(width) {
|
|
var csg = CSG.fromPolygons(TCAD.utils.createBox(width));
|
|
return TCAD.utils.createSolid(csg);
|
|
};
|
|
|
|
TCAD.utils.toCsgGroups = function(polygons) {
|
|
var groups = [];
|
|
for (var i = 0; i < polygons.length; i++) {
|
|
var p = polygons[i];
|
|
if (p.holes.length === 0) {
|
|
groups.push( new TCAD.CSGGroup([new TCAD.SimplePolygon(p.shell, p.normal)], p.normal) );
|
|
} else {
|
|
// TODO: triangulation needed
|
|
groups.push( new TCAD.CSGGroup([new TCAD.SimplePolygon(p.shell, p.normal)], p.normal) );
|
|
}
|
|
}
|
|
return groups;
|
|
};
|
|
|
|
TCAD.utils.checkPolygon = function(poly) {
|
|
if (poly.length < 3) {
|
|
throw new Error('Polygon should contain at least 3 point');
|
|
}
|
|
};
|
|
|
|
TCAD.utils.createPoint = function(x, y, z) {
|
|
// var g = new THREE.PlaneGeometry(0.05, 0.05);
|
|
// var m = new THREE.MeshBasicMaterial({color: 0x0000ff, side: THREE.DoubleSide});
|
|
// return new THREE.Mesh(g, m);
|
|
|
|
var material = new THREE.ShaderMaterial({
|
|
// color: 0xff0000,
|
|
// linewidth: 5
|
|
vertexShader :
|
|
'void main() {\n\t' +
|
|
'gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );' +
|
|
'gl_PointSize =10.0;\n\t' +
|
|
'\n}',
|
|
|
|
fragmentShader :
|
|
'void main() {\n\t' +
|
|
"vec2 coord = gl_PointCoord - vec2(0.5); //from [0,1] to [-0.5,0.5]\n" +
|
|
"if(length(coord) > 0.5) //outside of circle radius?\n" +
|
|
" discard;\n"+
|
|
"else\n"+
|
|
" gl_FragColor = vec4( 1.0, 0.0, 0.0, 1.0 );\n"
|
|
+'\n}'
|
|
});
|
|
|
|
var geometry = new THREE.Geometry();
|
|
geometry.vertices.push(new THREE.Vector3(x, y, z));
|
|
// geometry.vertices.push(new THREE.Vector3(x+.001, y+.001, z+.001));
|
|
|
|
// var line = new THREE.PointCloud(geometry, material);
|
|
// line.position.x = x;
|
|
// line.position.y = y;
|
|
// line.position.z = z;
|
|
// return line;
|
|
|
|
material = new THREE.SpriteMaterial( { color: 0xffffff, fog: false } );
|
|
var sprite = new THREE.Sprite( material );
|
|
sprite.position.set( x, y, z );
|
|
return sprite;
|
|
};
|
|
|
|
TCAD.utils.createLine = function (a, b, color) {
|
|
var material = new THREE.LineBasicMaterial({
|
|
color: color,
|
|
linewidth: 1
|
|
});
|
|
var geometry = new THREE.Geometry();
|
|
geometry.vertices.push(new THREE.Vector3(a.x, a.y, a.z));
|
|
geometry.vertices.push(new THREE.Vector3(b.x, b.y, b.z));
|
|
return new THREE.Line(geometry, material);
|
|
};
|
|
|
|
TCAD.utils.createPoint = function (x, y, z) {
|
|
var geometry = new THREE.SphereGeometry( 5, 16, 16 );
|
|
var material = new THREE.MeshBasicMaterial( {color: 0xff0000} );
|
|
var sphere = new THREE.Mesh(geometry, material);
|
|
sphere.position.x = x;
|
|
sphere.position.y = y;
|
|
sphere.position.z = z;
|
|
return sphere;
|
|
};
|
|
|
|
TCAD.utils.createSolidMaterial = function() {
|
|
return new THREE.MeshPhongMaterial({
|
|
vertexColors: THREE.FaceColors,
|
|
color: TCAD.view.FACE_COLOR,
|
|
shininess: 0,
|
|
polygonOffset : true,
|
|
polygonOffsetFactor : 1,
|
|
polygonOffsetUnits : 2,
|
|
side : THREE.DoubleSide
|
|
});
|
|
};
|
|
|
|
TCAD.utils.createSolid = function(csg) {
|
|
var material = TCAD.utils.createSolidMaterial();
|
|
return new TCAD.Solid(csg, material);
|
|
};
|
|
|
|
TCAD.utils.intercept = function(obj, methodName, aspect) {
|
|
var originFunc = obj[methodName];
|
|
obj[methodName] = function() {
|
|
var $this = this;
|
|
aspect(function() {originFunc.apply($this, arguments)}, arguments);
|
|
}
|
|
};
|
|
|
|
TCAD.utils.createPlane = function(basis, depth) {
|
|
var tu = TCAD.utils;
|
|
|
|
var initWidth = 1;
|
|
var boundingPolygon = [
|
|
new TCAD.Vector(0, 0, 0),
|
|
new TCAD.Vector(initWidth, 0, 0),
|
|
new TCAD.Vector(initWidth, initWidth, 0),
|
|
new TCAD.Vector(0, initWidth, 0)
|
|
];
|
|
var shared = tu.createShared();
|
|
|
|
var material = tu.createSolidMaterial();
|
|
material.transparent = true;
|
|
material.opacity = 0.5;
|
|
material.side = THREE.DoubleSide;
|
|
|
|
var tr = new TCAD.Matrix().setBasis(basis);
|
|
var currentBounds = new TCAD.BBox();
|
|
var points = boundingPolygon.map(function(p) { p.z = depth; return tr._apply(p); });
|
|
var polygon = new CSG.Polygon(points.map(function(p){return new CSG.Vertex(TCAD.utils.csgVec(p))}), shared);
|
|
var plane = new TCAD.Solid(CSG.fromPolygons([polygon]), material, 'PLANE');
|
|
plane.wireframeGroup.visible = false;
|
|
plane.mergeable = false;
|
|
var _3d = tr.invert();
|
|
|
|
function setBounds(bbox) {
|
|
var corner = new TCAD.Vector(bbox.minX, bbox.minY, 0);
|
|
var size = new TCAD.Vector(bbox.width(), bbox.height(), 1);
|
|
_3d._apply(size);
|
|
_3d._apply(corner);
|
|
plane.mesh.scale.set(size.x, size.y, size.z);
|
|
plane.mesh.position.set(corner.x, corner.y, corner.z);
|
|
currentBounds = bbox;
|
|
var poly = new CSG.Polygon(bbox.toPolygon().map(function(p){return new CSG.Vertex(TCAD.utils.csgVec( _3d._apply(p) ))}), shared);
|
|
plane.csg = CSG.fromPolygons([poly]);
|
|
}
|
|
var bb = new TCAD.BBox();
|
|
bb.checkBounds(-400, -400);
|
|
bb.checkBounds( 400, 400);
|
|
setBounds(bb);
|
|
|
|
var sketchFace = plane.polyFaces[0];
|
|
tu.intercept(sketchFace, 'syncSketches', function(invocation, args) {
|
|
var geom = args[0];
|
|
invocation(geom);
|
|
var bbox = new TCAD.BBox();
|
|
var connections = geom.connections.concat(TCAD.utils.arrFlatten1L(geom.loops));
|
|
for (var i = 0; i < connections.length; ++i) {
|
|
var l = connections[i];
|
|
bbox.checkBounds(l.a.x, l.a.y);
|
|
bbox.checkBounds(l.b.x, l.b.y);
|
|
}
|
|
if (bbox.maxX > currentBounds.maxX || bbox.maxY > currentBounds.maxY || bbox.minX < currentBounds.minX || bbox.minY < currentBounds.minY) {
|
|
bbox.expand(50);
|
|
setBounds(bbox);
|
|
}
|
|
});
|
|
|
|
return plane;
|
|
};
|
|
|
|
|
|
TCAD.utils.fixCCW = function(path, normal) {
|
|
var _2DTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(path, normal)).invert();
|
|
var path2D = [];
|
|
for (var i = 0; i < path.length; ++i) {
|
|
path2D[i] = _2DTransformation.apply(path[i]);
|
|
}
|
|
|
|
if (!TCAD.geom.isCCW(path2D)) {
|
|
path = path.slice(0);
|
|
path.reverse();
|
|
}
|
|
return path;
|
|
};
|
|
|
|
TCAD.TOLERANCE = 1E-6;
|
|
|
|
TCAD.utils.areEqual = function(v1, v2, tolerance) {
|
|
return Math.abs(v1 - v2) < tolerance;
|
|
};
|
|
|
|
TCAD.utils.areVectorsEqual = function(v1, v2, tolerance) {
|
|
return TCAD.utils.areEqual(v1.x, v2.x, tolerance) &&
|
|
TCAD.utils.areEqual(v1.y, v2.y, tolerance) &&
|
|
TCAD.utils.areEqual(v1.z, v2.z, tolerance);
|
|
};
|
|
|
|
TCAD.utils.vectorsEqual = function(v1, v2) {
|
|
return TCAD.utils.areVectorsEqual(v1, v2, TCAD.TOLERANCE);
|
|
};
|
|
|
|
TCAD.utils.equal = function(v1, v2) {
|
|
return TCAD.utils.areEqual(v1, v2, TCAD.TOLERANCE);
|
|
};
|
|
|
|
TCAD.utils.strictEqual = function(a, b) {
|
|
return a.x == b.x && a.y == b.y && a.z == b.z;
|
|
};
|
|
|
|
TCAD.utils.isPointInsidePolygon = function( inPt, inPolygon ) {
|
|
var EPSILON = TCAD.TOLERANCE;
|
|
|
|
var polyLen = inPolygon.length;
|
|
|
|
// inPt on polygon contour => immediate success or
|
|
// toggling of inside/outside at every single! intersection point of an edge
|
|
// with the horizontal line through inPt, left of inPt
|
|
// not counting lowerY endpoints of edges and whole edges on that line
|
|
var inside = false;
|
|
for( var p = polyLen - 1, q = 0; q < polyLen; p = q ++ ) {
|
|
var edgeLowPt = inPolygon[ p ];
|
|
var edgeHighPt = inPolygon[ q ];
|
|
|
|
var edgeDx = edgeHighPt.x - edgeLowPt.x;
|
|
var edgeDy = edgeHighPt.y - edgeLowPt.y;
|
|
|
|
if ( Math.abs(edgeDy) > EPSILON ) { // not parallel
|
|
if ( edgeDy < 0 ) {
|
|
edgeLowPt = inPolygon[ q ]; edgeDx = - edgeDx;
|
|
edgeHighPt = inPolygon[ p ]; edgeDy = - edgeDy;
|
|
}
|
|
if ( ( inPt.y < edgeLowPt.y ) || ( inPt.y > edgeHighPt.y ) ) continue;
|
|
|
|
if ( inPt.y == edgeLowPt.y ) {
|
|
if ( inPt.x == edgeLowPt.x ) return true; // inPt is on contour ?
|
|
// continue; // no intersection or edgeLowPt => doesn't count !!!
|
|
} else {
|
|
var perpEdge = edgeDy * (inPt.x - edgeLowPt.x) - edgeDx * (inPt.y - edgeLowPt.y);
|
|
if ( perpEdge == 0 ) return true; // inPt is on contour ?
|
|
if ( perpEdge < 0 ) continue;
|
|
inside = ! inside; // true intersection left of inPt
|
|
}
|
|
} else { // parallel or colinear
|
|
if ( inPt.y != edgeLowPt.y ) continue; // parallel
|
|
// egde lies on the same horizontal line as inPt
|
|
if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
|
|
( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) return true; // inPt: Point on contour !
|
|
// continue;
|
|
}
|
|
}
|
|
|
|
return inside;
|
|
};
|
|
|
|
TCAD.utils.sketchToPolygons = function(geom) {
|
|
|
|
var dict = TCAD.struct.hashTable.forVector2d();
|
|
var edges = TCAD.struct.hashTable.forDoubleArray();
|
|
|
|
var lines = geom.connections;
|
|
|
|
function edgeKey(a, b) {
|
|
return [a.x, a.y, b.x, b.y];
|
|
}
|
|
|
|
var size = 0;
|
|
var points = [];
|
|
function memDir(a, b) {
|
|
var dirs = dict.get(a);
|
|
if (dirs === null) {
|
|
dirs = [];
|
|
dict.put(a, dirs);
|
|
points.push(a);
|
|
}
|
|
dirs.push(b);
|
|
}
|
|
|
|
for (var i = 0; i < lines.length; i++) {
|
|
var a = lines[i].a;
|
|
var b = lines[i].b;
|
|
|
|
memDir(a, b);
|
|
memDir(b, a);
|
|
edges.put(edgeKey(a, b), lines[i]);
|
|
}
|
|
|
|
var graph = {
|
|
|
|
connections : function(e) {
|
|
var dirs = dict.get(e);
|
|
return dirs === null ? [] : dirs;
|
|
},
|
|
|
|
at : function(index) {
|
|
return points[index];
|
|
},
|
|
|
|
size : function() {
|
|
return points.length;
|
|
}
|
|
};
|
|
|
|
var loops = TCAD.graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF);
|
|
var polygons = [];
|
|
for (var li = 0; li < loops.length; ++li) {
|
|
var loop = loops[li];
|
|
if (!TCAD.geom.isCCW(loop)) loop.reverse();
|
|
var polyPoints = [];
|
|
for (var pi = 0; pi < loop.length; ++pi) {
|
|
var point = loop[pi];
|
|
var next = loop[(pi + 1) % loop.length];
|
|
|
|
var edge = edges.get(edgeKey(point, next));
|
|
if (edge === null) {
|
|
edge = edges.get(edgeKey(next, point));
|
|
}
|
|
polyPoints.push(point);
|
|
point.sketchConnectionObject = edge.sketchObject;
|
|
}
|
|
if (polyPoints.length >= 3) {
|
|
polygons.push(polyPoints);
|
|
} else {
|
|
console.warn("Points count < 3!");
|
|
}
|
|
}
|
|
for (var li = 0; li < geom.loops.length; ++li) {
|
|
var loop = geom.loops[li];
|
|
var polyPoints = loop.slice(0);
|
|
for (var si = 0; si < polyPoints.length; si++) {
|
|
var conn = polyPoints[si];
|
|
//reuse a point and ignore b point since it's a guaranteed loop
|
|
conn.a.sketchConnectionObject = conn.sketchObject;
|
|
polyPoints[si] = conn.a;
|
|
}
|
|
// we assume that connection object is the same al other the loop. That's why reverse is safe.
|
|
if (!TCAD.geom.isCCW(polyPoints)) polyPoints.reverse();
|
|
if (polyPoints.length >= 3) {
|
|
polygons.push(polyPoints);
|
|
}
|
|
}
|
|
return polygons;
|
|
};
|
|
|
|
TCAD.geom = {};
|
|
|
|
TCAD.geom.someBasis2 = function(normal) {
|
|
var x = normal.cross(normal.randomNonParallelVector());
|
|
var y = normal.cross(x).unit();
|
|
return [x, y, normal];
|
|
};
|
|
|
|
TCAD.geom.someBasis = function(twoPointsOnPlane, normal) {
|
|
var a = twoPointsOnPlane[0];
|
|
var b = twoPointsOnPlane[1];
|
|
|
|
var x = b.minus(a).normalize();
|
|
var y = normal.cross(x).normalize();
|
|
|
|
return [x, y, normal];
|
|
};
|
|
|
|
TCAD.geom.normalOfCCWSeq = function(ccwSequence) {
|
|
var a = ccwSequence[0];
|
|
var b = ccwSequence[1];
|
|
var c = ccwSequence[2];
|
|
|
|
return b.minus(a).cross(c.minus(a)).normalize();
|
|
};
|
|
|
|
TCAD.geom.normalOfCCWSeqTHREE = function(ccwSequence) {
|
|
var a = ccwSequence[0];
|
|
var b = ccwSequence[1].clone();
|
|
var c = ccwSequence[2].clone();
|
|
|
|
return b.sub(a).cross(c.sub(a)).normalize();
|
|
};
|
|
|
|
|
|
// http://en.wikipedia.org/wiki/Shoelace_formula
|
|
TCAD.geom.area = function (contour) {
|
|
var n = contour.length;
|
|
var a = 0.0;
|
|
for ( var p = n - 1, q = 0; q < n; p = q ++ ) {
|
|
a += contour[ p ].x * contour[ q ].y - contour[ q ].x * contour[ p ].y;
|
|
}
|
|
return a * 0.5;
|
|
};
|
|
|
|
TCAD.geom.isCCW = function(path2D) {
|
|
return TCAD.geom.area(path2D) >= 0;
|
|
};
|
|
|
|
TCAD.BBox = function() {
|
|
this.minX = Number.MAX_VALUE;
|
|
this.minY = Number.MAX_VALUE;
|
|
this.maxX = -Number.MAX_VALUE;
|
|
this.maxY = -Number.MAX_VALUE;
|
|
this.checkBounds = function(x, y) {
|
|
this.minX = Math.min(this.minX, x);
|
|
this.minY = Math.min(this.minY, y);
|
|
this.maxX = Math.max(this.maxX, x);
|
|
this.maxY = Math.max(this.maxY, y);
|
|
};
|
|
|
|
this.center = function() {
|
|
return new TCAD.Vector(this.minX + (this.maxX - this.minX) / 2, this.minY + (this.maxY - this.minY) / 2, 0)
|
|
};
|
|
|
|
this.width = function() {
|
|
return this.maxX - this.minX;
|
|
};
|
|
|
|
this.height = function() {
|
|
return this.maxY - this.minY;
|
|
};
|
|
|
|
this.expand = function(delta) {
|
|
this.minX -= delta;
|
|
this.minY -= delta;
|
|
this.maxX += delta;
|
|
this.maxY += delta;
|
|
};
|
|
|
|
this.toPolygon = function() {
|
|
return [
|
|
new TCAD.Vector(this.minX, this.minY, 0),
|
|
new TCAD.Vector(this.maxX, this.minY, 0),
|
|
new TCAD.Vector(this.maxX, this.maxY, 0),
|
|
new TCAD.Vector(this.minX, this.maxY, 0)
|
|
];
|
|
}
|
|
};
|
|
|
|
TCAD.geom.calculateExtrudedLid = function(sourcePolygon, normal, direction, expansionFactor) {
|
|
var lid = [];
|
|
var length = sourcePolygon.length;
|
|
var work;
|
|
var si;
|
|
if (!!expansionFactor && expansionFactor != 1) {
|
|
var source2d = [];
|
|
work = [];
|
|
|
|
var _3dTr = new TCAD.Matrix().setBasis(TCAD.geom.someBasis2(new CSG.Vector3D(normal))); // use passed basis
|
|
var _2dTr = _3dTr.invert();
|
|
var sourceBBox = new TCAD.BBox();
|
|
var workBBox = new TCAD.BBox();
|
|
for (si = 0; si < length; ++si) {
|
|
var sourcePoint = _2dTr.apply(sourcePolygon[si]);
|
|
source2d[si] = sourcePoint;
|
|
work[si] = sourcePoint.multiply(expansionFactor);
|
|
work[si].z = source2d[si].z = 0;
|
|
sourceBBox.checkBounds(sourcePoint.x, sourcePoint.y);
|
|
workBBox.checkBounds(work[si].x, work[si].y)
|
|
}
|
|
var alignVector = workBBox.center().minus(sourceBBox.center());
|
|
var depth = normal.dot(sourcePolygon[0]);
|
|
for (si = 0; si < length; ++si) {
|
|
work[si] = work[si].minus(alignVector);
|
|
work[si].z = depth;
|
|
work[si] = _3dTr.apply(work[si]);
|
|
}
|
|
} else {
|
|
work = sourcePolygon;
|
|
}
|
|
|
|
for (si = 0; si < length; ++si) {
|
|
lid[si] = work[si].plus(direction);
|
|
}
|
|
|
|
return lid;
|
|
};
|
|
|
|
TCAD.geom.extrude = function(source, sourceNormal, target, expansionFactor) {
|
|
|
|
var extrudeDistance = target.normalize().dot(sourceNormal);
|
|
if (extrudeDistance == 0) {
|
|
return [];
|
|
}
|
|
var negate = extrudeDistance < 0;
|
|
|
|
var poly = [null, null];
|
|
var lid = TCAD.geom.calculateExtrudedLid(source, sourceNormal, target, expansionFactor);
|
|
|
|
var bottom, top;
|
|
if (negate) {
|
|
bottom = lid;
|
|
top = source;
|
|
} else {
|
|
bottom = source;
|
|
top = lid;
|
|
}
|
|
|
|
var n = source.length;
|
|
for ( var p = n - 1, i = 0; i < n; p = i ++ ) {
|
|
var shared = TCAD.utils.createShared();
|
|
shared.__tcad.csgInfo = {derivedFrom: source[p].sketchConnectionObject};
|
|
var face = new CSG.Polygon([
|
|
new CSG.Vertex(TCAD.utils.csgVec(bottom[p])),
|
|
new CSG.Vertex(TCAD.utils.csgVec(bottom[i])),
|
|
new CSG.Vertex(TCAD.utils.csgVec(top[i])),
|
|
new CSG.Vertex(TCAD.utils.csgVec(top[p]))
|
|
], shared);
|
|
poly.push(face);
|
|
}
|
|
|
|
var bottomNormal, topNormal;
|
|
if (negate) {
|
|
lid.reverse();
|
|
bottomNormal = sourceNormal;
|
|
topNormal = sourceNormal.negate();
|
|
} else {
|
|
source = source.slice(0);
|
|
source.reverse();
|
|
bottomNormal = sourceNormal.negate();
|
|
topNormal = sourceNormal;
|
|
}
|
|
|
|
function vecToVertex(v) {
|
|
return new CSG.Vertex(TCAD.utils.csgVec(v));
|
|
}
|
|
|
|
var sourcePlane = new CSG.Plane(bottomNormal.csg(), bottomNormal.dot(source[0]));
|
|
var lidPlane = new CSG.Plane(topNormal.csg(), topNormal.dot(lid[0]));
|
|
|
|
poly[0] = new CSG.Polygon(source.map(vecToVertex), TCAD.utils.createShared(), sourcePlane);
|
|
poly[1] = new CSG.Polygon(lid.map(vecToVertex), TCAD.utils.createShared(), lidPlane);
|
|
return poly;
|
|
};
|
|
|
|
TCAD.geom.SOLID_COUNTER = 0;
|
|
|
|
TCAD.geom.triangulate = function(path, normal) {
|
|
var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis2(normal));
|
|
var _2dTransformation = _3dTransformation.invert();
|
|
var i;
|
|
var shell = [];
|
|
for (i = 0; i < path.length; ++i) {
|
|
shell[i] = _2dTransformation.apply(path[i].pos);
|
|
}
|
|
var myTriangulator = new PNLTRI.Triangulator();
|
|
return myTriangulator.triangulate_polygon( [ shell ] );
|
|
// return THREE.Shape.utils.triangulateShape( f2d.shell, f2d.holes );
|
|
};
|
|
|
|
TCAD.utils.groupCSG = function(csg) {
|
|
var csgPolygons = csg.toPolygons();
|
|
var groups = {};
|
|
for (var i = 0; i < csgPolygons.length; i++) {
|
|
var p = csgPolygons[i];
|
|
var tag = p.shared.getTag();
|
|
if (groups[tag] === undefined) {
|
|
groups[tag] = {
|
|
tag : tag,
|
|
polygons : [],
|
|
shared : p.shared,
|
|
plane : p.plane
|
|
};
|
|
}
|
|
groups[tag].polygons.push(p);
|
|
}
|
|
return groups;
|
|
};
|
|
|
|
TCAD.utils.SHARED_COUNTER = 0;
|
|
TCAD.utils.createShared = function() {
|
|
var id = TCAD.utils.SHARED_COUNTER ++;
|
|
var shared = new CSG.Polygon.Shared([id, id, id, id]);
|
|
shared.__tcad = {};
|
|
return shared;
|
|
};
|
|
|
|
TCAD.utils.isSmoothPiece = function(shared) {
|
|
return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom &&
|
|
(shared.__tcad.csgInfo.derivedFrom._class === 'TCAD.TWO.Arc' || shared.__tcad.csgInfo.derivedFrom._class === 'TCAD.TWO.Circle');
|
|
};
|
|
|
|
TCAD.utils.sameID = function(id1, id2) {
|
|
if (id1 === null || id2 === null) {
|
|
return false;
|
|
}
|
|
return id1 === id2;
|
|
};
|
|
|
|
TCAD.utils.getDerivedID = function(shared) {
|
|
return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom.id : null;
|
|
};
|
|
|
|
TCAD.utils.getDerivedFrom = function(shared) {
|
|
return shared.__tcad && !!shared.__tcad.csgInfo && !!shared.__tcad.csgInfo.derivedFrom ? shared.__tcad.csgInfo.derivedFrom : null;
|
|
};
|
|
|
|
/** @constructor */
|
|
TCAD.Solid = function(csg, material, type) {
|
|
csg = csg.reTesselated().canonicalized();
|
|
this.tCadType = type || 'SOLID';
|
|
this.csg = csg;
|
|
|
|
this.cadGroup = new THREE.Object3D();
|
|
this.cadGroup.__tcad_solid = this;
|
|
|
|
var geometry = new THREE.Geometry();
|
|
geometry.dynamic = true;
|
|
this.mesh = new THREE.Mesh(geometry, material);
|
|
this.cadGroup.add(this.mesh);
|
|
|
|
this.tCadId = TCAD.geom.SOLID_COUNTER ++;
|
|
this.faceCounter = 0;
|
|
|
|
this.wireframeGroup = new THREE.Object3D();
|
|
this.cadGroup.add(this.wireframeGroup);
|
|
|
|
this.polyFaces = [];
|
|
this.wires = TCAD.struct.hashTable.forEdge();
|
|
this.curvedSurfaces = {};
|
|
this.mergeable = true;
|
|
|
|
this.setupGeometry();
|
|
};
|
|
|
|
TCAD.Solid.prototype.setupGeometry = function() {
|
|
function threeV(v) {return new THREE.Vector3( v.x, v.y, v.z )}
|
|
|
|
var off = 0;
|
|
var groups = TCAD.utils.groupCSG(this.csg);
|
|
var geom = this.mesh.geometry;
|
|
for (var gIdx in groups) {
|
|
var group = groups[gIdx];
|
|
if (group.shared.__tcad === undefined) group.shared.__tcad = {};
|
|
var polyFace = new TCAD.SketchFace(this, group);
|
|
this.polyFaces.push(polyFace);
|
|
for (var p = 0; p < group.polygons.length; ++p) {
|
|
var poly = group.polygons[p];
|
|
var vLength = poly.vertices.length;
|
|
if (vLength < 3) continue;
|
|
var firstVertex = poly.vertices[0];
|
|
geom.vertices.push(threeV(firstVertex.pos));
|
|
geom.vertices.push(threeV(poly.vertices[1].pos));
|
|
var normal = threeV(poly.plane.normal);
|
|
for (var i = 2; i < vLength; i++) {
|
|
geom.vertices.push(threeV(poly.vertices[i].pos));
|
|
|
|
var a = off;
|
|
var b = i - 1 + off;
|
|
var c = i + off;
|
|
var face = new THREE.Face3(a, b, c);
|
|
polyFace.faces.push(face);
|
|
face.__TCAD_polyFace = polyFace;
|
|
face.normal = normal;
|
|
face.materialIndex = gIdx;
|
|
geom.faces.push(face);
|
|
//face.color.set(new THREE.Color().setRGB( Math.random(), Math.random(), Math.random()));
|
|
}
|
|
//TCAD.view.setFaceColor(polyFace, TCAD.utils.isSmoothPiece(group.shared) ? 0xFF0000 : null);
|
|
off = geom.vertices.length;
|
|
}
|
|
this.collectCurvedSurface(polyFace);
|
|
this.collectWires(polyFace);
|
|
}
|
|
|
|
geom.mergeVertices();
|
|
|
|
this.processWires();
|
|
};
|
|
|
|
TCAD.Solid.prototype.vanish = function() {
|
|
this.cadGroup.parent.remove( this.cadGroup );
|
|
this.mesh.material.dispose();
|
|
this.mesh.geometry.dispose();
|
|
};
|
|
|
|
TCAD.Solid.prototype.collectCurvedSurface = function(face) {
|
|
var derivedFrom = TCAD.utils.getDerivedFrom(face.csgGroup.shared);
|
|
if (derivedFrom === null || derivedFrom._class !== "TCAD.TWO.Arc" && derivedFrom._class !== "TCAD.TWO.Circle" ) return;
|
|
var surfaces = this.curvedSurfaces[derivedFrom.id];
|
|
if (surfaces === undefined) {
|
|
surfaces = [];
|
|
this.curvedSurfaces[derivedFrom.id] = surfaces;
|
|
}
|
|
surfaces.push(face);
|
|
face.curvedSurfaces = surfaces;
|
|
};
|
|
|
|
TCAD.Solid.prototype.collectWires = function(face) {
|
|
|
|
function contains(planes, plane) {
|
|
for (var j = 0; j < planes.length; j++) {
|
|
if (planes[j].equals(plane)) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
var paths = TCAD.craft.reconstructSketchBounds(this.csg, face, true);
|
|
for (var i = 0; i < paths.length; i++) {
|
|
var path = paths[i];
|
|
var p, q, n = path.vertices.length;
|
|
for (q = 0, p = n - 1; q < n; p = q++) {
|
|
var edge = [path.vertices[p], path.vertices[q]];
|
|
var data = this.wires.get(edge);
|
|
|
|
if (data === null) {
|
|
data = {
|
|
sharedPlanes : [face.csgGroup.plane],
|
|
sharedFaces : [face]
|
|
};
|
|
this.wires.put(edge, data);
|
|
} else {
|
|
if (!contains(data.sharedPlanes, face.csgGroup.plane)) {
|
|
data.sharedPlanes.push(face.csgGroup.plane);
|
|
}
|
|
data.sharedFaces.push(face);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
TCAD.Solid.SMOOTH_LIMIT = 10 * Math.PI / 180;
|
|
|
|
TCAD.Solid.prototype.processWires = function() {
|
|
var solid = this;
|
|
this.wires.entries(function(edge, data) {
|
|
var u = TCAD.utils;
|
|
if (data.sharedPlanes.length > 1) {
|
|
var plane0 = data.sharedPlanes[0];
|
|
var plane1 = data.sharedPlanes[1];
|
|
var angle = Math.acos(plane0.normal.dot(plane1.normal));
|
|
if (angle < TCAD.Solid.SMOOTH_LIMIT) {
|
|
return;
|
|
}
|
|
}
|
|
for (var i = 0; i < data.sharedFaces.length; ++i) {
|
|
for (var j = i + 1; j < data.sharedFaces.length; ++j) {
|
|
var face0 = data.sharedFaces[0];
|
|
var face1 = data.sharedFaces[1];
|
|
if (u.sameID(u.getDerivedID(face0.csgGroup.shared), u.getDerivedID(face1.csgGroup.shared))) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
solid.addLineToScene(edge[0], edge[1]);
|
|
});
|
|
};
|
|
|
|
TCAD.Solid.prototype.addLineToScene = function(a, b) {
|
|
var lg = new THREE.Geometry();
|
|
lg.vertices.push(a);
|
|
lg.vertices.push(b);
|
|
var line = new THREE.Line(lg, TCAD.SketchFace.prototype.WIREFRAME_MATERIAL);
|
|
this.wireframeGroup.add(line);
|
|
};
|
|
|
|
/** @constructor */
|
|
TCAD.SketchFace = function(solid, csgGroup) {
|
|
csgGroup.__face = this;
|
|
if (csgGroup.shared.__tcad.faceId === undefined) {
|
|
this.id = solid.tCadId + ":" + (solid.faceCounter++);
|
|
} else {
|
|
this.id = csgGroup.shared.__tcad.faceId;
|
|
}
|
|
csgGroup.shared.__tcad.faceId = this.id;
|
|
|
|
this.solid = solid;
|
|
this.csgGroup = csgGroup;
|
|
this.faces = [];
|
|
this.sketch3DGroup = null;
|
|
this.curvedSurfaces = null;
|
|
};
|
|
|
|
if (typeof THREE !== "undefined") {
|
|
TCAD.SketchFace.prototype.SKETCH_MATERIAL = new THREE.LineBasicMaterial({
|
|
color: 0xFFFFFF, linewidth: 3/TCAD.DPR});
|
|
TCAD.SketchFace.prototype.WIREFRAME_MATERIAL = new THREE.LineBasicMaterial({
|
|
color: 0x2B3856, linewidth: 3/TCAD.DPR});
|
|
}
|
|
|
|
TCAD.SketchFace.prototype.calcBasis = function() {
|
|
var vec = TCAD.utils.vec;
|
|
var normal = vec(this.csgGroup.plane.normal);
|
|
var alignPlane, x, y;
|
|
if (Math.abs(normal.dot(TCAD.math.AXIS.Y)) < 0.5) {
|
|
alignPlane = normal.cross(TCAD.math.AXIS.Y);
|
|
} else {
|
|
alignPlane = normal.cross(TCAD.math.AXIS.Z);
|
|
}
|
|
y = alignPlane.cross(normal);
|
|
x = y.cross(normal);
|
|
return [x, y, normal];
|
|
};
|
|
|
|
TCAD.SketchFace.prototype.basis = function() {
|
|
if (!this._basis) {
|
|
this._basis = this.calcBasis();
|
|
}
|
|
return this._basis;
|
|
//return TCAD.geom.someBasis(this.csgGroup.polygons[0].vertices.map(function (v) {
|
|
// return vec(v.pos)
|
|
//}), vec(this.csgGroup.plane.normal));
|
|
};
|
|
|
|
TCAD.SketchFace.prototype.syncSketches = function(geom) {
|
|
var i;
|
|
var normal = this.csgGroup.plane.normal;
|
|
var offVector = normal.scale(0); // disable it. use polygon offset feature of material
|
|
|
|
if (this.sketch3DGroup != null) {
|
|
for (var i = this.sketch3DGroup.children.length - 1; i >= 0; --i) {
|
|
this.sketch3DGroup.remove(this.sketch3DGroup.children[i]);
|
|
}
|
|
} else {
|
|
this.sketch3DGroup = new THREE.Object3D();
|
|
this.solid.cadGroup.add(this.sketch3DGroup);
|
|
}
|
|
|
|
var basis = this.basis();
|
|
var _3dTransformation = new TCAD.Matrix().setBasis(basis);
|
|
//we lost depth or z off in 2d sketch, calculate it again
|
|
var depth = this.csgGroup.plane.w;
|
|
var connections = geom.connections.concat(TCAD.utils.arrFlatten1L(geom.loops));
|
|
for (i = 0; i < connections.length; ++i) {
|
|
var l = connections[i];
|
|
var lg = new THREE.Geometry();
|
|
l.a.z = l.b.z = depth;
|
|
var a = _3dTransformation.apply(l.a);
|
|
var b = _3dTransformation.apply(l.b);
|
|
|
|
lg.vertices.push(a.plus(offVector).three());
|
|
lg.vertices.push(b.plus(offVector).three());
|
|
var line = new THREE.Line(lg, this.SKETCH_MATERIAL);
|
|
this.sketch3DGroup.add(line);
|
|
}
|
|
};
|
|
|
|
TCAD.POLYGON_COUNTER = 0;
|
|
/** @constructor */
|
|
TCAD.Polygon = function(shell, holes, normal) {
|
|
this.id = TCAD.POLYGON_COUNTER ++;
|
|
if (!holes) {
|
|
holes = [];
|
|
}
|
|
TCAD.utils.checkPolygon(shell);
|
|
for (var h = 0; h < holes.length; ++h) {
|
|
TCAD.utils.checkPolygon(holes[h]);
|
|
}
|
|
|
|
if (normal === undefined) {
|
|
normal = TCAD.geom.normalOfCCWSeq(shell);
|
|
} else {
|
|
shell = TCAD.utils.fixCCW(shell, normal);
|
|
if (holes.length > 0) {
|
|
var neg = normal.negate();
|
|
for (var h = 0; h < holes.length; ++h) {
|
|
holes[h] = TCAD.utils.fixCCW(holes[h], neg);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
this.normal = normal;
|
|
this.shell = shell;
|
|
this.holes = holes;
|
|
};
|
|
|
|
TCAD.Polygon.prototype.reverse = function(triangle) {
|
|
var first = triangle[0];
|
|
triangle[0] = triangle[2];
|
|
triangle[2] = first;
|
|
};
|
|
|
|
TCAD.Polygon.prototype.flip = function() {
|
|
return new TCAD.Polygon(this.shell, this.holes, this.normal.negate());
|
|
};
|
|
|
|
TCAD.Polygon.prototype.shift = function(target) {
|
|
var shell = [];
|
|
var i;
|
|
for (i = 0; i < this.shell.length; ++i) {
|
|
shell[i] = this.shell[i].plus(target);
|
|
}
|
|
var holes = [];
|
|
for (var h = 0; h < this.holes.length; ++h) {
|
|
holes[h] = [];
|
|
for (i = 0; i < this.holes[h].length; ++i) {
|
|
holes[h][i] = this.holes[h][i].plus(target);
|
|
}
|
|
}
|
|
return new TCAD.Polygon(shell, holes, this.normal);
|
|
};
|
|
|
|
TCAD.Polygon.prototype.get2DTransformation = function() {
|
|
var _3dTransformation = new TCAD.Matrix().setBasis(TCAD.geom.someBasis(this.shell, this.normal));
|
|
var _2dTransformation = _3dTransformation.invert();
|
|
return _2dTransformation;
|
|
};
|
|
|
|
TCAD.Polygon.prototype.to2D = function() {
|
|
|
|
var _2dTransformation = this.get2DTransformation();
|
|
|
|
var i, h;
|
|
var shell = [];
|
|
var holes = [];
|
|
for (i = 0; i < this.shell.length; ++i) {
|
|
shell[i] = _2dTransformation.apply(this.shell[i]);
|
|
}
|
|
for (h = 0; h < this.holes.length; ++h) {
|
|
holes[h] = [];
|
|
for (i = 0; i < this.holes[h].length; ++i) {
|
|
holes[h][i] = _2dTransformation.apply(this.holes[h][i]);
|
|
}
|
|
}
|
|
return {shell: shell, holes: holes};
|
|
};
|
|
|
|
TCAD.Polygon.prototype.collectPaths = function(paths) {
|
|
paths.push(this.shell);
|
|
paths.push.apply(paths, this.holes);
|
|
};
|
|
|
|
TCAD.Polygon.prototype.triangulate = function() {
|
|
|
|
function triangulateShape( contour, holes ) {
|
|
var myTriangulator = new PNLTRI.Triangulator();
|
|
return myTriangulator.triangulate_polygon( [ contour ].concat(holes) );
|
|
}
|
|
|
|
var i, h;
|
|
var f2d = this.to2D();
|
|
|
|
for (i = 0; i < f2d.shell.length; ++i) {
|
|
f2d.shell[i] = f2d.shell[i].three();
|
|
}
|
|
for (h = 0; h < f2d.holes.length; ++h) {
|
|
for (i = 0; i < f2d.holes[h].length; ++i) {
|
|
f2d.holes[h][i] = f2d.holes[h][i].three();
|
|
}
|
|
}
|
|
return triangulateShape( f2d.shell, f2d.holes );
|
|
// return THREE.Shape.utils.triangulateShape( f2d.shell, f2d.holes );
|
|
};
|
|
|
|
TCAD.Polygon.prototype.eachVertex = function(handler) {
|
|
var i, h;
|
|
for (i = 0; i < this.shell.length; ++i) {
|
|
if (handler(this.shell, i) === true) return;
|
|
}
|
|
for (h = 0; h < this.holes.length; ++h) {
|
|
for (i = 0; i < this.holes[h].length; ++i) {
|
|
if (handler(this.holes[h], i) === true) return;
|
|
}
|
|
}
|
|
};
|
|
|
|
/** @constructor */
|
|
TCAD.Sketch = function() {
|
|
this.group = new THREE.Object3D();
|
|
};
|
|
|
|
TCAD.utils.iteratePath = function(path, shift, callback) {
|
|
var p, q, n = path.length;
|
|
for (p = n - 1,q = 0;q < n; p = q++) {
|
|
var ai = (p + shift) % n;
|
|
var bi = (q + shift) % n;
|
|
if (!callback(path[ai], path[bi], ai, bi, q, path)) {
|
|
break
|
|
}
|
|
}
|
|
};
|
|
|
|
TCAD.utils.addAll = function(arr, arrToAdd) {
|
|
for (var i = 0; i < arrToAdd.length; i++) {
|
|
arr.push(arrToAdd[i]);
|
|
}
|
|
};
|
|
|
|
TCAD.utils.arrFlatten1L = function(arr) {
|
|
var result = [];
|
|
for (var i = 0; i < arr.length; i++) {
|
|
TCAD.utils.addAll(result, arr[i]);
|
|
}
|
|
return result;
|
|
};
|