diff --git a/web/app/3d/cad-utils.js b/web/app/3d/cad-utils.js index a0728e84..8e833594 100644 --- a/web/app/3d/cad-utils.js +++ b/web/app/3d/cad-utils.js @@ -225,96 +225,6 @@ export function fixCCW(path, normal) { export const isPointInsidePolygon = math.isPointInsidePolygon; -export function sketchToPolygons(geom) { - - var dict = HashTable.forVector2d(); - var edges = 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 = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF); - var polygons = []; - var li, loop, polyPoints; - for (li = 0; li < loops.length; ++li) { - loop = loops[li]; - if (!isCCW(loop)) loop.reverse(); - 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 (li = 0; li < geom.loops.length; ++li) { - loop = geom.loops[li]; - 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 (!isCCW(polyPoints)) polyPoints.reverse(); - if (polyPoints.length >= 3) { - polygons.push(polyPoints); - } - } - return polygons; -} - export function someBasis2(normal) { var x = normal.cross(normal.randomNonParallelVector()); var y = normal.cross(x).unit(); diff --git a/web/app/3d/craft/brep/cut-extrude.js b/web/app/3d/craft/brep/cut-extrude.js index 710cd0b1..a8a3ca70 100644 --- a/web/app/3d/craft/brep/cut-extrude.js +++ b/web/app/3d/craft/brep/cut-extrude.js @@ -1,13 +1,15 @@ import {Matrix3, BasisForPlane, ORIGIN} from '../../../math/l3space' import * as math from '../../../math/math' import Vector from '../../../math/vector' -import {Extruder} from '../../../brep/brep-builder' +import {enclose, iterateSegments} from '../../../brep/brep-builder' import {BREPValidator} from '../../../brep/brep-validator' import * as stitching from '../../../brep/stitching' import {subtract, union} from '../../../brep/operations/boolean' import {Loop} from '../../../brep/topo/loop' import {Shell} from '../../../brep/topo/shell' -import {ReadSketchFromFace} from './sketch-reader' +import {CompositeCurve} from '../../../brep/geom/curve' +import {ReadSketchContoursFromFace} from '../sketch/sketch-reader' +import {Segment} from '../sketch/sketch-model' import {isCurveClass} from '../../cad-utils' import {BREPSceneSolid} from '../../scene/brep-scene-object' @@ -23,19 +25,11 @@ export function Cut(app, params) { export function doOperation(app, params, cut) { const face = app.findFace(params.face); const solid = face.solid; - let reverseNormal = !cut; - let normal = face.normal(); - if (params.value < 0) { - params = fixNegativeValue(params); - reverseNormal = !reverseNormal; - } - - if (reverseNormal) normal = normal.negate(); - const sketch = ReadSketchFromFace(app, face, reverseNormal); + const sketch = ReadSketchContoursFromFace(app, face); - const extruder = new ParametricExtruder(params); - const operand = combineShells(sketch.map(s => extruder.extrude(s, normal))); + const details = getEncloseDetails(params, sketch, face.brepFace.surface, !cut); + const operand = combineShells(details.map(d => enclose(d.basePath, d.lidPath, d.baseSurface, d.lidSurface, wallJoiner))); BREPValidator.validateToConsole(operand); let result; @@ -59,14 +53,6 @@ export function doOperation(app, params, cut) { } } -export function fixNegativeValue(params) { - if (params.value < 0) { - params = Object.assign({}, params); - params.value *= -1; - } - return params; -} - function combineShells(shells) { if (shells.length == 1) { return shells[0]; @@ -76,47 +62,61 @@ function combineShells(shells) { return cutter; } -export class ParametricExtruder extends Extruder { - - constructor(params) { - super(); - this.params = params; - } - - prepareLidCalculation(baseNormal, lidNormal) { - let target; - this.basis = BasisForPlane(baseNormal); - if (this.params.rotation != 0) { - target = Matrix3.rotateMatrix(this.params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(lidNormal); - if (this.params.angle != 0) { - target = Matrix3.rotateMatrix(this.params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target); - } - target._multiply(Math.abs(this.params.value)); - } else { - target = lidNormal.multiply(Math.abs(this.params.value)); - } - this.target = target; - } - - calculateLid(basePoints, baseNormal, lidNormal) { - if (this.params.prism != 1) { - const scale = this.params.prism; - - const _3Dtr = new Matrix3().setBasis(this.basis); - const _2Dtr = _3Dtr.invert(); - const poly2d = basePoints.map(p => _2Dtr.apply(p)); - basePoints = math.polygonOffset(poly2d, scale).map(p => _3Dtr.apply(p)); - } - return basePoints.map(p => p.plus(this.target)); - } - - onWallCallback(wallFace, baseHalfEdge) { - const conn = baseHalfEdge.vertexA.point.sketchConnectionObject; - if (conn && isCurveClass(conn._class)) { - if (!conn.stitchedSurface) { - conn.stitchedSurface = new stitching.StitchedSurface(); - } - conn.stitchedSurface.addFace(wallFace); +export function wallJoiner(wallFace, group) { + if (group && group.constructor.name != 'Segment') { + if (!group.stitchedSurface) { + group.stitchedSurface = new stitching.StitchedSurface(); } + group.stitchedSurface.addFace(wallFace); } } + +export function getEncloseDetails(params, contours, sketchSurface, invert) { + let value = params.value; + if (value < 0) { + value = Math.abs(value); + invert = !invert; + } + + const baseSurface = invert ? sketchSurface.invert() : sketchSurface; + + let target; + const targetDir = baseSurface.normal.negate(); + + if (params.rotation != 0) { + target = Matrix3.rotateMatrix(params.rotation * Math.PI / 180, this.basis[0], ORIGIN).apply(targetDir); + if (params.angle != 0) { + target = Matrix3.rotateMatrix(params.angle * Math.PI / 180, this.basis[2], ORIGIN)._apply(target); + } + target._multiply(value); + } else { + target = targetDir.multiply(value); + } + + let details = []; + for (let contour of contours) { + if (invert) { + contour.reverse(); + } + const basePath = contour.transferOnSurface(sketchSurface); + const lidPath = new CompositeCurve(); + + let lidPoints = basePath.points; + if (!math.equal(params.prism, 1)) { + const _3D = sketchSurface.get3DTransformation(); + const _2D = _3D.invert(); + lidPoints = math.polygonOffset(lidPoints.map(p => _2D.apply(p)) , params.prism).map(p => _3D._apply(p)); + } + + for (let i = 0; i < basePath.points.length; ++i) { + const curve = basePath.curves[i]; + const point = lidPoints[i]; + const group = basePath.groups[i]; + lidPath.add(curve.translate(target), point.plus(target), group); + } + + const lidSurface = baseSurface.translate(target).invert(); + details.push({basePath, lidPath, baseSurface, lidSurface}); + } + return details; +} diff --git a/web/app/3d/craft/brep/sketch-reader.js b/web/app/3d/craft/brep/sketch-reader.js deleted file mode 100644 index 7b3d0d20..00000000 --- a/web/app/3d/craft/brep/sketch-reader.js +++ /dev/null @@ -1,9 +0,0 @@ -import {sortPolygons, getSketchedPolygons3D} from '../mesh/workbench' - - - -// here will be function of conversion sketch objects to brep DS - -export function ReadSketchFromFace(app, face, reverseGeom) { - return getSketchedPolygons3D(app, face, reverseGeom); -} \ No newline at end of file diff --git a/web/app/3d/craft/brep/wizards/cut-extrude.js b/web/app/3d/craft/brep/wizards/cut-extrude.js index 17893537..682b9122 100644 --- a/web/app/3d/craft/brep/wizards/cut-extrude.js +++ b/web/app/3d/craft/brep/wizards/cut-extrude.js @@ -1,8 +1,10 @@ import {CURRENT_SELECTION as S} from './wizard' import {PreviewWizard, SketchBasedPreviewer} from './preview-wizard' -import {ParametricExtruder, fixNegativeValue} from '../cut-extrude' +import {getEncloseDetails} from '../cut-extrude' import {TriangulatePolygons} from '../../../triangulation' import Vector from '../../../../math/vector' +import {reversedIndex} from '../../../../utils/utils' + const METADATA = [ ['value' , 'number', 50], @@ -50,31 +52,23 @@ export class ExtrudePreviewer extends SketchBasedPreviewer { } createImpl(app, params, sketch, face) { - const normal = face.normal(); - let reverseNormal = this.inversed; - if (params.value < 0) { - params = fixNegativeValue(params); - reverseNormal = !reverseNormal; - } - const parametricExtruder = new ParametricExtruder(params); - const baseNormal = reverseNormal ? normal : normal.negate(); - const lidNormal = reverseNormal ? baseNormal.negate() : normal; - - parametricExtruder.prepareLidCalculation(baseNormal, lidNormal); - - const triangles = []; - for (let base of sketch) { - var lid = parametricExtruder.calculateLid(base); + const encloseDetails = getEncloseDetails(params, sketch, face.brepFace.surface, !this.inversed); + const triangles = []; + for (let d of encloseDetails) { + const base = d.basePath.points; + const lid = d.lidPath.points; const n = base.length; for (let p = n - 1, q = 0; q < n; p = q ++) { triangles.push([ base[p], base[q], lid[q] ]); triangles.push([ lid[q], lid[p], base[p] ]); } - TriangulatePolygons([base], baseNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr)) - .forEach(tr => triangles.push(tr)); - TriangulatePolygons([lid], lidNormal, (v) => v.toArray(), (arr) => new Vector().set3(arr)) - .forEach(tr => triangles.push(tr)); + function collectOnSurface(points, normal) { + TriangulatePolygons([points], normal, (v) => v.toArray(), (arr) => new Vector().set3(arr)) + .forEach(tr => triangles.push(tr)); + } + collectOnSurface(base, d.baseSurface.normal); + collectOnSurface(lid, d.lidSurface.normal); } return triangles; } diff --git a/web/app/3d/craft/brep/wizards/preview-wizard.js b/web/app/3d/craft/brep/wizards/preview-wizard.js index 7774b004..75606ca8 100644 --- a/web/app/3d/craft/brep/wizards/preview-wizard.js +++ b/web/app/3d/craft/brep/wizards/preview-wizard.js @@ -1,5 +1,5 @@ import {Wizard} from './wizard' -import {ReadSketchFromFace} from '../sketch-reader' +import {ReadSketchContoursFromFace} from '../../sketch/sketch-reader' import {Loop} from '../../../../brep/topo/loop' export class PreviewWizard extends Wizard { @@ -77,7 +77,7 @@ export class SketchBasedPreviewer { if (!face) return null; const needSketchRead = !this.sketch || params.face != this.face; if (needSketchRead) { - this.sketch = ReadSketchFromFace(app, face); + this.sketch = ReadSketchContoursFromFace(app, face, false); //for (let polygon of this.sketch) { //if (!Loop.isPolygonCCWOnSurface(polygon, face.brepFace.surface) && this.fixToCCW) { // polygon.reverse(); diff --git a/web/app/3d/craft/mesh/workbench.js b/web/app/3d/craft/mesh/workbench.js index 25673ed7..628b2cc2 100644 --- a/web/app/3d/craft/mesh/workbench.js +++ b/web/app/3d/craft/mesh/workbench.js @@ -9,197 +9,6 @@ import {LoadSTLFromURL} from '../../io' import revolve from './revolve' import {Triangulate} from '../../triangulation' -function SketchConnection(a, b, sketchObject) { - this.a = a; - this.b = b; - this.sketchObject = sketchObject; -} - -export function readSketchGeom(sketch, faceId, readConstructionSegments) { - let idCounter = 0; - function createData(obj) { - return {_class : obj._class, id : faceId + ":" + (idCounter ++)} - } - - const RESOLUTION = 20; - const out = {connections : [], loops : [], constructionSegments: []}; - if (sketch.layers !== undefined) { - for (let layer of sketch.layers) { - const isConstructionLayer = layer.name == "_construction_"; - if (isConstructionLayer && !readConstructionSegments) continue; - for (let obj of layer.data) { - if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue; - if (obj.edge !== undefined) continue; - if (!!obj.aux) continue; - if (obj._class === 'TCAD.TWO.Segment') { - const segA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0); - const segB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0); - const sketchConnection = new SketchConnection(segA, segB, createData(obj)); - if (isConstructionLayer) { - out.constructionSegments.push(sketchConnection); - } else { - out.connections.push(sketchConnection); - } - } else if (obj._class === 'TCAD.TWO.Arc') { - const arcA = new Vector(obj.points[0][1][1], obj.points[0][2][1], 0); - const arcB = new Vector(obj.points[1][1][1], obj.points[1][2][1], 0); - const arcCenter = new Vector(obj.points[2][1][1], obj.points[2][2][1], 0); - const approxedArc = approxArc(arcA, arcB, arcCenter, RESOLUTION); - const arcData = createData(obj); - for (let j = 0; j < approxedArc.length - 1; j++) { - out.connections.push(new SketchConnection(approxedArc[j], approxedArc[j+1], arcData)); - } - } else if (obj._class === 'TCAD.TWO.EllipticalArc') { - const ep1 = ReadSketchPoint(obj.ep1); - const ep2 = ReadSketchPoint(obj.ep2); - const a = ReadSketchPoint(obj.a); - const b = ReadSketchPoint(obj.b); - const r = obj.r; - const approxedEllArc = approxEllipticalArc(ep1, ep2, a, b, r, RESOLUTION); - const arcData = createData(obj); - for (let j = 0; j < approxedEllArc.length - 1; j++) { - out.connections.push(new SketchConnection(approxedEllArc[j], approxedEllArc[j+1], arcData)); - } - } else if (obj._class === 'TCAD.TWO.BezierCurve') { - const a = ReadSketchPoint(obj.a); - const b = ReadSketchPoint(obj.b); - const cp1 = ReadSketchPoint(obj.cp1); - const cp2 = ReadSketchPoint(obj.cp2); - const approxedCurve = approxBezierCurve(a, b, cp1, cp2, RESOLUTION); - const curvedData = createData(obj); - for (let j = 0; j < approxedCurve.length - 1; j++) { - out.connections.push(new SketchConnection(approxedCurve[j], approxedCurve[j+1], curvedData)); - } - } else if (obj._class === 'TCAD.TWO.Circle') { - const circleCenter = new Vector(obj.c[1][1], obj.c[2][1], 0); - const approxedCircle = approxCircle(circleCenter, obj.r, RESOLUTION); - const circleData = createData(obj); - const loop = []; - let p, q, n = approxedCircle.length; - for (p = n - 1, q = 0; q < n; p = q++) { - loop.push(new SketchConnection(approxedCircle[p], approxedCircle[q], circleData)); - } - out.loops.push(loop); - } - } - } - } - return out; -} - -function ReadSketchPoint(arr) { - return new Vector(arr[1][1], arr[2][1], 0) -} - -export function approxArc(ao, bo, c, resolution) { - var a = ao.minus(c); - var b = bo.minus(c); - var points = [ao]; - var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); - if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; - if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; - - var r = a.length(); - resolution = 1; - //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); - var step = resolution / (2 * Math.PI); - var k = Math.round(abAngle / step); - var angle = Math.atan2(a.y, a.x) + step; - - for (var i = 0; i < k - 1; ++i) { - points.push(new Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); - angle += step; - } - points.push(bo); - return points; -} - -export function approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) { - const axisX = ep2.minus(ep1); - const radiusX = axisX.length() * 0.5; - axisX._normalize(); - const c = ep1.plus(axisX.multiply(radiusX)); - const a = ao.minus(c); - const b = bo.minus(c); - const points = [ao]; - const rotation = Math.atan2(axisX.y, axisX.x); - let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); - if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; - if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; - - const sq = (a) => a * a; - - resolution = 1; - - const step = resolution / (2 * Math.PI); - const k = Math.round(abAngle / step); - let angle = Math.atan2(a.y, a.x) + step - rotation; - - for (let i = 0; i < k - 1; ++i) { - const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY))); - points.push(new Vector(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation))); - angle += step; - } - points.push(bo); - return points; -} - -export function approxCircle(c, r, resolution) { - var points = []; - - resolution = 1; - //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); - var step = resolution / (2 * Math.PI); - var k = Math.round((2 * Math.PI) / step); - - for (var i = 0, angle = 0; i < k; ++i, angle += step) { - points.push(new Vector(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); - } - return points; -} - -export function approxBezierCurve(a, b, cp1, cp2, resolution) { - return LUT(a, b, cp1, cp2, 10); -} - -export function getSketchedPolygons3D(app, face, reverseGeom) { - - var savedFace = localStorage.getItem(app.faceStorageKey(face.id)); - if (savedFace == null) return null; - - var geom = readSketchGeom(JSON.parse(savedFace), face.id, false); - var polygons2D = cad_utils.sketchToPolygons(geom); - if (reverseGeom) { - polygons2D.forEach(p => p.reverse()); - } - var depth = null; - var sketchedPolygons = []; - for (var i = 0; i < polygons2D.length; i++) { - var poly2D = polygons2D[i]; - if (poly2D.length < 3) continue; - - if (depth == null) { - var _3dTransformation = new Matrix3().setBasis(face.basis()); - //we lost depth or z off in 2d sketch, calculate it again - depth = face.depth(); - } - - var polygon = []; - polygon._2D = poly2D; - for (var m = 0; m < poly2D.length; ++m) { - var vec = poly2D[m]; - vec.z = depth; -// var a = _3dTransformation.apply(new Vector(poly2D[m][0], poly2D[m][1], depth)); - var a = _3dTransformation.apply(vec); - a.sketchConnectionObject = vec.sketchConnectionObject; - polygon.push(a); - } - - sketchedPolygons.push(polygon); - } - return sketchedPolygons; -} - export function sortPolygons(polygons) { function Loop(polygon) { this.polygon = polygon; diff --git a/web/app/3d/craft/sketch/sketch-model.js b/web/app/3d/craft/sketch/sketch-model.js new file mode 100644 index 00000000..f80bc5c4 --- /dev/null +++ b/web/app/3d/craft/sketch/sketch-model.js @@ -0,0 +1,252 @@ +import {CompositeCurve} from '../../../brep/geom/curve' +import {ApproxCurve} from '../../../brep/geom/impl/approx' +import {Point} from '../../../brep/geom/point' +import {Line} from '../../../brep/geom/impl/Line' +import {LUT} from '../../../math/bezier-cubic' +import {isCCW} from '../../../math/math' + +const RESOLUTION = 20; + +class SketchPrimitive { + constructor(id) { + this.id = id; + this.inverted = false; + } + + invert() { + this.inverted = !this.inverted; + } + + approximate(resolution) { + const approximation = this.approximateImpl(resolution); + if (this.inverted) { + approximation.reverse(); + } + return approximation; + } + + isCurve() { + return this.constructor.name != 'Segment'; + } +} + +export class Segment extends SketchPrimitive { + constructor(id, a, b) { + super(id); + this.a = a; + this.b = b; + } + + approximateImpl(resolution) { + return [this.a, this.b]; + } +} + +export class Arc extends SketchPrimitive { + constructor(id, a, b, c) { + super(id); + this.a = a; + this.b = b; + this.c = c; + } + + approximateImpl(resolution) { + return Arc.approximateArc(this.a, this.b, this.c, resolution); + } + + static approximateArc(ao, bo, c, resolution) { + var a = ao.minus(c); + var b = bo.minus(c); + var points = [ao]; + var abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); + if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; + if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; + + var r = a.length(); + resolution = 1; + //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); + var step = resolution / (2 * Math.PI); + var k = Math.round(abAngle / step); + var angle = Math.atan2(a.y, a.x) + step; + + for (var i = 0; i < k - 1; ++i) { + points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); + angle += step; + } + points.push(bo); + return points; + } +} + +export class BezierCurve extends SketchPrimitive { + constructor(id, a, b, cp1, cp2) { + super(id); + this.a = a; + this.b = b; + this.cp1 = cp1; + this.cp2 = cp2; + } + + approximateImpl(resolution) { + return LUT(this.a, this.b, this.cp1, this.cp2, 10); + } +} + +export class EllipticalArc extends SketchPrimitive { + constructor(id, ep1, ep2, a, b, r) { + super(id); + this.ep1 = ep1; + this.ep2 = ep2; + this.a = a; + this.b = b; + this.r = r; + } + + approximateImpl(resolution) { + return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.a, this.b, this.r, resolution); + } + + static approxEllipticalArc(ep1, ep2, ao, bo, radiusY, resolution) { + const axisX = ep2.minus(ep1); + const radiusX = axisX.length() * 0.5; + axisX._normalize(); + const c = ep1.plus(axisX.multiply(radiusX)); + const a = ao.minus(c); + const b = bo.minus(c); + const points = [ao]; + const rotation = Math.atan2(axisX.y, axisX.x); + let abAngle = Math.atan2(b.y, b.x) - Math.atan2(a.y, a.x); + if (abAngle > Math.PI * 2) abAngle = Math.PI / 2 - abAngle; + if (abAngle < 0) abAngle = Math.PI * 2 + abAngle; + + const sq = (a) => a * a; + + resolution = 1; + + const step = resolution / (2 * Math.PI); + const k = Math.round(abAngle / step); + let angle = Math.atan2(a.y, a.x) + step - rotation; + + for (let i = 0; i < k - 1; ++i) { + const r = Math.sqrt(1/( sq(Math.cos(angle)/radiusX) + sq(Math.sin(angle)/radiusY))); + points.push(new Point(c.x + r*Math.cos(angle + rotation), c.y + r*Math.sin(angle + rotation))); + angle += step; + } + points.push(bo); + return points; + } + +} + +export class Circle extends SketchPrimitive { + constructor(id, c, r) { + super(id); + this.c = c; + this.r = r; + } + + approximateImpl(resolution) { + return Circle.approxCircle(this.c, this.r, resolution); + } + + static approxCircle(c, r, resolution) { + var points = []; + resolution = 1; + //var step = Math.acos(1 - ((resolution * resolution) / (2 * r * r))); + var step = resolution / (2 * Math.PI); + var k = Math.round((2 * Math.PI) / step); + + for (var i = 0, angle = 0; i < k; ++i, angle += step) { + points.push(new Point(c.x + r*Math.cos(angle), c.y + r*Math.sin(angle))); + } + points.push(points[0]); // close it + return points; + } +} + +export class Ellipse extends SketchPrimitive { + constructor(id, ep1, ep2, r) { + super(id); + this.ep1 = ep1; + this.ep2 = ep2; + this.r = r; + } + + approximateImpl(resolution) { + return EllipticalArc.approxEllipticalArc(this.ep1, this.ep2, this.ep1, this.ep1, this.r, resolution); + } +} + +export class Contour { + + constructor() { + this.segments = []; + } + + add(obj) { + this.segments.push(obj); + } + + transferOnSurface(surface, forceApproximation) { + const cc = new CompositeCurve(); + + const _3dTransformation = surface.get3DTransformation(); + const depth = surface.w; + function tr(v) { + v = v.copy(); + v.z = depth; + return _3dTransformation._apply(v); + } + + let prev = null; + let firstPoint = null; + for (let segIdx = 0; segIdx < this.segments.length; ++segIdx) { + let segment = this.segments[segIdx]; + let approximation = segment.approximate(RESOLUTION); + + approximation = approximation.map(p => tr(p)); + + const n = approximation.length; + prev = prev == null ? approximation[0] : prev; + approximation[0] = prev; // this magic is to keep identity of same vectors + if (firstPoint == null) firstPoint = approximation[0]; + + if (segIdx == this.segments.length - 1) { + approximation[n - 1] = firstPoint; + } + + if (!forceApproximation && segment.constructor.name == 'Arc') { + cc.add(new ApproxCurve(approximation, segment), prev, segment); + prev = approximation[n - 1]; + } else { + for (let i = 1; i < n; ++i) { + const curr = approximation[i]; + cc.add(new Line.fromSegment(prev, curr), prev, segment); + prev = curr; + } + } + } + return cc; + } + + approximate(resolution) { + const approximation = []; + for (let segment of this.segments) { + const segmentApproximation = segment.approximate(resolution); + //skip last one cuz it's guaranteed to be closed + for (let i = 0; i < segmentApproximation.length - 1; ++i) { + approximation.push(segmentApproximation[i]); + } + } + return approximation; + } + + isCCW() { + return isCCW(this.approximate(10)); + } + + reverse() { + this.segments.reverse(); + this.segments.forEach(s => s.invert()); + } +} diff --git a/web/app/3d/craft/sketch/sketch-reader.js b/web/app/3d/craft/sketch/sketch-reader.js new file mode 100644 index 00000000..b96b1ef1 --- /dev/null +++ b/web/app/3d/craft/sketch/sketch-reader.js @@ -0,0 +1,170 @@ +import * as sm from './sketch-model' +import {Matrix3, AXIS, ORIGIN} from '../../../math/l3space' +import Vector from '../../../math/vector' +import {Graph} from '../../../math/graph' +import * as math from '../../../math/math' +import {HashTable} from '../../../utils/hashmap' + +export function ReadSketch(sketch, faceId, readConstructionSegments) { + let idCounter = 0; + function genID() { + return faceId + ":" + (idCounter++); + } + const out = {connections : [], loops : [], constructionSegments: []}; + if (sketch.layers !== undefined) { + for (let layer of sketch.layers) { + const isConstructionLayer = layer.name == "_construction_"; + if (isConstructionLayer && !readConstructionSegments) continue; + for (let obj of layer.data) { + if (isConstructionLayer && obj._class !== 'TCAD.TWO.Segment') continue; + if (obj.edge !== undefined) continue; + if (!!obj.aux) continue; + if (obj._class === 'TCAD.TWO.Segment') { + const segA = ReadSketchPoint(obj.points[0]); + const segB = ReadSketchPoint(obj.points[1]); + const pushOn = isConstructionLayer ? out.constructionSegments : out.connections; + pushOn.push(new sm.Segment(genID(), segA, segB)); + } else if (obj._class === 'TCAD.TWO.Arc') { + const arcA = ReadSketchPoint(obj.points[0]); + const arcB = ReadSketchPoint(obj.points[1]); + const arcCenter = ReadSketchPoint(obj.points[2]); + out.connections.push(new sm.Arc(genID(), arcA, arcB, arcCenter)); + } else if (obj._class === 'TCAD.TWO.EllipticalArc') { + const ep1 = ReadSketchPoint(obj.ep1); + const ep2 = ReadSketchPoint(obj.ep2); + const a = ReadSketchPoint(obj.a); + const b = ReadSketchPoint(obj.b); + out.connections.push(new sm.EllipticalArc(genID(), ep1, ep2, a, b, obj.r)); + } else if (obj._class === 'TCAD.TWO.BezierCurve') { + const a = ReadSketchPoint(obj.a); + const b = ReadSketchPoint(obj.b); + const cp1 = ReadSketchPoint(obj.cp1); + const cp2 = ReadSketchPoint(obj.cp2); + out.connections.push(new sm.BezierCurve(genID(), a, b, cp1, cp2)); + } else if (obj._class === 'TCAD.TWO.Circle') { + const circleCenter = ReadSketchPoint(obj.c); + out.loops.push(new sm.Circle(genID(), circleCenter, obj.r)); + } else if (obj._class === 'TCAD.TWO.Ellipse') { + const ep1 = ReadSketchPoint(obj.ep1); + const ep2 = ReadSketchPoint(obj.ep2); + out.loops.push(new sm.Ellipse(genID(), ep1, ep2, obj.r)); + } + } + } + } + return out; +} + +export function ReadSketchPoint(arr) { + return new Vector(arr[1][1], arr[2][1], 0) +} + +export function ReadSketchContoursFromFace(app, face) { + const savedFace = localStorage.getItem(app.faceStorageKey(face.id)); + if (savedFace == null) return null; + const geom = ReadSketch(JSON.parse(savedFace), face.id, false); + const contours = findClosedContours(geom.connections); + for (let loop of geom.loops) { + const contour = new sm.Contour(); + contour.add(loop); + contours.push(contour); + } + for (let contour of contours) { + if (!contour.isCCW()) contour.reverse(); + } + return contours; +} + +function findClosedContours(segments) { + const result = []; + findClosedContoursFromPairedCurves(segments, result); + findClosedContoursFromGraph(segments, result); + return result; +} + +function findClosedContoursFromPairedCurves(segments, result) { + for (let i = 0; i < segments.length; i++) { + const s1 = segments[i]; + for (let j = i; j < segments.length; j++) { + if (i == j) continue; + const s2 = segments[j]; + if (s1.isCurve() && s2.isCurve()) { + let paired = false; + if (math.strictEqual2D(s1.a, s2.a) && math.strictEqual2D(s1.b, s2.b)) { + paired = true; + s2.invert(); + } else if (math.strictEqual2D(s1.a, s2.b) && math.strictEqual2D(s1.b, s2.a)) { + paired = true; + } + if (paired) { + const contour = new sm.Contour(); + contour.add(s1); + contour.add(s2); + result.push(contour); + } + } + } + } +} + +function findClosedContoursFromGraph(segments, result) { + + const dict = HashTable.forVector2d(); + const edges = HashTable.forDoubleArray(); + + function edgeKey(a, b) { + return [a.x, a.y, b.x, b.y]; + } + + const points = []; + function memDir(a, b) { + let dirs = dict.get(a); + if (dirs === null) { + dirs = []; + dict.put(a, dirs); + points.push(a); + } + dirs.push(b); + } + + for (let seg of segments) { + const a = seg.a; + const b = seg.b; + + memDir(a, b); + memDir(b, a); + edges.put(edgeKey(a, b), seg); + } + + const graph = { + + connections : function(e) { + const dirs = dict.get(e); + return dirs === null ? [] : dirs; + }, + + at : function(index) { + return points[index]; + }, + + size : function() { + return points.length; + } + }; + + const loops = Graph.findAllLoops(graph, dict.hashCodeF, dict.equalsF); + for (let loop of loops) { + const contour = new sm.Contour(); + for (let pi = 0; pi < loop.length; ++pi) { + const point = loop[pi]; + const next = loop[(pi + 1) % loop.length]; + let edge = edges.get(edgeKey(point, next)); + if (edge === null) { + edge = edges.get(edgeKey(next, point)); + edge.invert(); + } + contour.add(edge); + } + result.push(contour); + } +} diff --git a/web/app/3d/modeler-app.js b/web/app/3d/modeler-app.js index 55ac63c7..3b55ecb4 100644 --- a/web/app/3d/modeler-app.js +++ b/web/app/3d/modeler-app.js @@ -9,6 +9,7 @@ import * as AllActions from './actions/all-actions' import Vector from '../math/vector' import {Matrix3, AXIS, ORIGIN, IDENTITY_BASIS} from '../math/l3space' import {Craft} from './craft/craft' +import {ReadSketch} from './craft/sketch/sketch-reader' import * as workbench from './craft/mesh/workbench' import * as cad_utils from './cad-utils' import * as math from '../math/math' @@ -598,7 +599,7 @@ App.prototype.refreshSketchOnFace = function(sketchFace) { var faceStorageKey = this.faceStorageKey(sketchFace.id); var savedFace = localStorage.getItem(faceStorageKey); if (savedFace != null) { - var geom = workbench.readSketchGeom(JSON.parse(savedFace), sketchFace.id, true); + var geom = ReadSketch(JSON.parse(savedFace), sketchFace.id, true); sketchFace.syncSketches(geom); } }; diff --git a/web/app/3d/scene/scene-object.js b/web/app/3d/scene/scene-object.js index c8c11d04..ea7df401 100644 --- a/web/app/3d/scene/scene-object.js +++ b/web/app/3d/scene/scene-object.js @@ -2,7 +2,7 @@ import {HashTable} from '../../utils/hashmap' import Vector from '../../math/vector' import Counters from '../counters' import {Matrix3, BasisForPlane} from '../../math/l3space' -import {arrFlatten1L, isCurveClass} from '../cad-utils' +import {isCurveClass} from '../cad-utils' import DPR from '../../utils/dpr' export class SceneSolid { @@ -61,6 +61,8 @@ export function createSolidMaterial(skin) { }, skin)); } +const OFF_LINES_VECTOR = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material + export class SceneFace { constructor(solid, propagatedId) { if (propagatedId === undefined) { @@ -109,9 +111,6 @@ export class SceneFace { } syncSketches(geom) { - const normal = this.normal(); - const offVector = new Vector();//normal.multiply(0); // disable it. use polygon offset feature of material - if (this.sketch3DGroup != null) { for (let i = this.sketch3DGroup.children.length - 1; i >= 0; --i) { this.sketch3DGroup.remove(this.sketch3DGroup.children[i]); @@ -125,34 +124,29 @@ export class SceneFace { const _3dTransformation = new Matrix3().setBasis(basis); //we lost depth or z off in 2d sketch, calculate it again const depth = this.depth(); - const polyLines = new Map(); - function addSketchConnections(connections, material) { - for (let i = 0; i < connections.length; ++i) { - const l = connections[i]; + const addSketchObjects = (sketchObjects, material, close) => { + for (let sketchObject of sketchObjects) { + let line = new THREE.Line(undefined, material); + line.__TCAD_SketchObject = sketchObject; + const chunks = sketchObject.approximate(10); + function addLine(p, q) { + const lg = line.geometry; + chunks[p].z = chunks[q].z = depth; + const a = _3dTransformation.apply(chunks[p]); + const b = _3dTransformation.apply(chunks[q]); - let line = polyLines.get(l.sketchObject.id); - if (!line) { - line = new THREE.Line(undefined, material); - line.__TCAD_SketchObject = l.sketchObject; - polyLines.set(l.sketchObject.id, line); + lg.vertices.push(a._plus(OFF_LINES_VECTOR).three()); + lg.vertices.push(b._plus(OFF_LINES_VECTOR).three()); } - const lg = line.geometry; - l.a.z = l.b.z = depth; - const a = _3dTransformation.apply(l.a); - const b = _3dTransformation.apply(l.b); - - lg.vertices.push(a.plus(offVector).three()); - lg.vertices.push(b.plus(offVector).three()); + for (let q = 1; q < chunks.length; q ++) { + addLine(q - 1, q); + } + this.sketch3DGroup.add(line); } - - } - addSketchConnections(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL); - addSketchConnections(geom.connections, SKETCH_MATERIAL); - addSketchConnections(arrFlatten1L(geom.loops), SKETCH_MATERIAL); - - for (let line of polyLines.values()) { - this.sketch3DGroup.add(line); - } + }; + addSketchObjects(geom.constructionSegments, SKETCH_CONSTRUCTION_MATERIAL); + addSketchObjects(geom.connections, SKETCH_MATERIAL); + addSketchObjects(geom.loops, SKETCH_MATERIAL); } findById(sketchObjectId) { diff --git a/web/app/brep/brep-builder.js b/web/app/brep/brep-builder.js index 9f2f477c..7221a1f6 100644 --- a/web/app/brep/brep-builder.js +++ b/web/app/brep/brep-builder.js @@ -4,9 +4,11 @@ import {Loop} from './topo/loop' import {Face} from './topo/face' import {HalfEdge, Edge} from './topo/edge' import {Line} from './geom/impl/line' +import {ApproxCurve, ApproxSurface} from './geom/impl/approx' import {Plane} from './geom/impl/plane' import {Point} from './geom/point' import {BasisForPlane, Matrix3} from '../math/l3space' +import {CompositeCurve} from './geom/curve' import * as cad_utils from '../3d/cad-utils' import * as math from '../math/math' @@ -25,95 +27,111 @@ function checkCCW(points, normal) { } export function createPrism(basePoints, height) { - return new SimpleExtruder(height).extrude(basePoints, cad_utils.normalOfCCWSeq(basePoints)); + const normal = cad_utils.normalOfCCWSeq(basePoints); + const baseSurface = new Plane(normal, normal.dot(basePoints[0])); + const extrudeVector = baseSurface.normal.multiply( - height); + const lidSurface = baseSurface.translate(extrudeVector).invert(); + const lidPoints = basePoints.map(p => p.plus(extrudeVector)); + const basePath = new CompositeCurve(); + const lidPath = new CompositeCurve(); + + for (let i = 0; i < basePoints.length; i++) { + let j = (i + 1) % basePoints.length; + basePath.add(Line.fromSegment(basePoints[i], basePoints[j]), basePoints[i], null); + lidPath.add(Line.fromSegment(lidPoints[i], lidPoints[j]), lidPoints[i], null); + } + return enclose(basePath, lidPath, baseSurface, lidSurface, () => {}); } -export class Extruder { - - prepareLidCalculation(baseNormal, lidNormal) { - } - - calculateLid(basePoints) { - throw 'not implemented'; - } - - extrude(basePoints, normal) { - const baseLoop = createPlaneLoop(basePoints.map(p => new Vertex(p))); - const baseFace = createPlaneFace(normal, baseLoop); - const lidNormal = normal.multiply(-1); - this.prepareLidCalculation(normal, lidNormal); +export function enclose(basePath, lidPath, baseSurface, lidSurface, onWallF) { - //iterateSegments(basePoints.map(p => new Vertex(p.plus(offVector))), (a, b) => lidSegments.push({a, b})); - const lidPoints = this.calculateLid(basePoints, normal, lidNormal).reverse(); - const lidLoop = createPlaneLoop(lidPoints.map(p => new Vertex(p))); + if (basePath.points.length != lidPath.points.length) { + throw 'illegal arguments'; + } + + const baseLoop = new Loop(); + const lidLoop = new Loop(); - const shell = new Shell(); + const shell = new Shell(); + const baseVertices = basePath.points.map(p => new Vertex(p)); + const lidVertices = lidPath.points.map(p => new Vertex(p)); - const n = baseLoop.halfEdges.length; - for (let i = 0; i < n; i++) { - let lidIdx = n - 2 - i; - if (lidIdx == -1) { - lidIdx = n - 1; - } - const baseHalfEdge = baseLoop.halfEdges[i]; - const lidHalfEdge = lidLoop.halfEdges[lidIdx]; - const wallPolygon = [baseHalfEdge.vertexB, baseHalfEdge.vertexA, lidHalfEdge.vertexB, lidHalfEdge.vertexA]; - const wallLoop = createPlaneLoop(wallPolygon); - - const baseEdge = new Edge(Line.fromSegment(baseHalfEdge.vertexA.point, baseHalfEdge.vertexB.point)); - linkHalfEdges(baseEdge, baseHalfEdge, wallLoop.halfEdges[0]); - - const lidEdge = new Edge(Line.fromSegment(lidHalfEdge.vertexA.point, lidHalfEdge.vertexB.point)); - linkHalfEdges(lidEdge, lidHalfEdge, wallLoop.halfEdges[2]); - - const wallNormal = cad_utils.normalOfCCWSeq(wallPolygon.map(v => v.point)); - - const wallFace = createPlaneFace(wallNormal, wallLoop); - wallFace.role = 'wall:' + i; - this.onWallCallback(wallFace, baseHalfEdge); - - shell.faces.push(wallFace); - } - const lidFace = createPlaneFace(lidNormal, lidLoop); - iterateSegments(shell.faces, (a, b) => { - const halfEdgeA = a.outerLoop.halfEdges[3]; - const halfEdgeB = b.outerLoop.halfEdges[1]; - const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point); - linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB); - }); - - baseFace.role = 'base'; - lidFace.role = 'lid'; + const n = basePath.points.length; + for (let i = 0; i < n; i++) { + let j = (i + 1) % n; + const baseHalfEdge = new HalfEdge().setAB(baseVertices[i], baseVertices[j]); + const lidHalfEdge = new HalfEdge().setAB(lidVertices[j], lidVertices[i]); - shell.faces.push(baseFace, lidFace); - shell.faces.forEach(f => f.shell = shell); - return shell; - } + baseHalfEdge.edge = new Edge(basePath.curves[i]); + lidHalfEdge.edge = new Edge(lidPath.curves[i]); - onWallCallback(wallFace, baseHalfEdge) { + baseHalfEdge.edge.halfEdge1 = baseHalfEdge; + lidHalfEdge.edge.halfEdge1 = lidHalfEdge; + + + baseHalfEdge.loop = baseLoop; + baseLoop.halfEdges.push(baseHalfEdge); + + lidHalfEdge.loop = lidLoop; + lidLoop.halfEdges[(n + n - 2 - i) % n] = lidHalfEdge; // keep old style order for the unit tests + + const wallFace = createFaceFromTwoEdges(createTwin(baseHalfEdge), createTwin(lidHalfEdge)); + + wallFace.role = 'wall:' + i; + onWallF(wallFace, basePath.groups[i]); + shell.faces.push(wallFace); } + + iterateSegments(shell.faces, (a, b) => { + const halfEdgeA = a.outerLoop.halfEdges[3]; + const halfEdgeB = b.outerLoop.halfEdges[1]; + const curve = Line.fromSegment(halfEdgeA.vertexA.point, halfEdgeA.vertexB.point); + linkHalfEdges(new Edge(curve), halfEdgeA, halfEdgeB); + }); + + linkSegments(baseLoop.halfEdges); + linkSegments(lidLoop.halfEdges); + + const baseFace = createFace(baseSurface, baseLoop); + const lidFace = createFace(lidSurface, lidLoop); + baseFace.role = 'base'; + lidFace.role = 'lid'; + + shell.faces.push(baseFace, lidFace); + shell.faces.forEach(f => f.shell = shell); + return shell; } -export class SimpleExtruder extends Extruder { - - constructor(height) { - super(); - this.height = height; +function createTwin(halfEdge) { + const twin = new HalfEdge(); + twin.vertexA = halfEdge.vertexB; + twin.vertexB = halfEdge.vertexA; + twin.edge = halfEdge.edge; + if (halfEdge.edge.halfEdge1 == halfEdge) { + halfEdge.edge.halfEdge2 = twin; + } else { + halfEdge.edge.halfEdge1 = twin; } + return twin; +} - prepareLidCalculation(baseNormal, lidNormal) { - this.extrudeVector = lidNormal.multiply(this.height); - } +function createFace(surface, loop) { + const face = new Face(surface); + face.outerLoop = loop; + loop.face = face; + return face; +} - calculateLid(basePoints) { - return basePoints.map(p => p.plus(this.extrudeVector)) - } + +function createPlaneForLoop(normal, loop) { + const w = loop.halfEdges[0].vertexA.point.dot(normal); + const plane = new Plane(normal, w); + return plane; } function createPlaneFace(normal, loop) { - const w = loop.halfEdges[0].vertexA.point.dot(normal); - const plane = new Plane(normal, w); + const plane = createPlaneForLoop(); const face = new Face(plane); face.outerLoop = loop; loop.face = face; @@ -128,23 +146,26 @@ export function linkHalfEdges(edge, halfEdge1, halfEdge2) { edge.halfEdge2 = halfEdge2; } -export function createPlaneLoop(vertices) { - +export function createLoopFromCompositeCurve(path) { // TODO: REMOVE! const loop = new Loop(); - - iterateSegments(vertices, (a, b) => { - createHalfEdge(loop, a, b) - }); - + const vertices = []; + for (let seg of path) { + vertices[seg.pos] = new Vertex(s.pointA); + } + for (let seg of path) { + const halfEdge = createHalfEdge(loop, vertices[seg.pos], vertices[seg.posNext]); + halfEdge.edge = new Edge(seg.curve); + halfEdge.edge.halfEdge1 = halfEdge; + } linkSegments(loop.halfEdges); return loop; } -export function createHalfEdge(loop, a, b) { +export function createHalfEdge(loop, vertexA, vertexB) { const halfEdge = new HalfEdge(); halfEdge.loop = loop; - halfEdge.vertexA = a; - halfEdge.vertexB = b; + halfEdge.vertexA = vertexA; + halfEdge.vertexB = vertexB; loop.halfEdges.push(halfEdge); return halfEdge; } @@ -177,3 +198,54 @@ export function invertLoop(loop) { loop.halfEdges.reverse(); linkSegments(loop.halfEdges); } + +export function createPlaneLoop(vertices) { + + const loop = new Loop(); + + iterateSegments(vertices, (a, b) => { + createHalfEdge(loop, a, b) + }); + + linkSegments(loop.halfEdges); + return loop; +} + +export function createFaceFromTwoEdges(e1, e2) { + const loop = new Loop(); + e1.loop = loop; + e2.loop = loop; + loop.halfEdges.push( + e1, + HalfEdge.create(e1.vertexB, e2.vertexA, loop), + e2, + HalfEdge.create(e2.vertexB, e1.vertexA, loop)); + + let surface = null; + if (e1.edge.curve.constructor.name == 'Line' && + e2.edge.curve.constructor.name == 'Line') { + const normal = cad_utils.normalOfCCWSeq(loop.halfEdges.map(e => e.vertexA.point)); + surface = createPlaneForLoop(normal, loop); + } else if ((e1.edge.curve instanceof ApproxCurve) && (e2.edge.curve instanceof ApproxCurve)) { + const chunk1 = e1.edge.curve.getChunk(e1.edge.vertexA.point, e1.edge.vertexB.point); + const chunk2 = e2.edge.curve.getChunk(e2.edge.vertexA.point, e2.edge.vertexB.point); + const n = chunk1.length; + if (n != chunk2.length) { + throw 'unsupported'; + } + surface = new ApproxSurface(); + for (let p = n - 1, q = 0; q < n; p = q ++) { + const polygon = [ chunk1[p], chunk1[q], chunk2[q], chunk2[p] ]; + surface.mesh.push(polygon); + } + } else { + throw 'unsupported'; + } + + linkSegments(loop.halfEdges); + + const face = new Face(surface); + face.outerLoop = loop; + loop.face = face; + return face; +} diff --git a/web/app/brep/geom/curve.js b/web/app/brep/geom/curve.js index 2bd5779f..a6513079 100644 --- a/web/app/brep/geom/curve.js +++ b/web/app/brep/geom/curve.js @@ -2,7 +2,6 @@ export class Curve { constructor() { - } intersectCurve(curve) { @@ -12,4 +11,24 @@ export class Curve { parametricEquation(t) { throw 'not implemented'; } -} \ No newline at end of file + + translate(vector) { + throw 'not implemented'; + } +} + + +export class CompositeCurve { + + constructor() { + this.curves = []; + this.points = []; + this.groups = []; + } + + add(curve, point, group) { + this.curves.push(curve); + this.points.push(point); + this.groups.push(group); + } +} diff --git a/web/app/brep/geom/impl/approx.js b/web/app/brep/geom/impl/approx.js new file mode 100644 index 00000000..93472c52 --- /dev/null +++ b/web/app/brep/geom/impl/approx.js @@ -0,0 +1,20 @@ +import {Surface} from '../surface' +import {Curve} from '../curve' +import {Matrix3, AXIS, BasisForPlane} from '../../../math/l3space' +import * as math from '../../../math/math' + +export class ApproxSurface extends Surface { + constructor(mesh) { + super(); + this.mesh = mesh; + } +} + +export class ApproxCurve extends Curve { + constructor(segments, proto) { + super(); + this.segments = segments; + this.proto = proto; + } + +} diff --git a/web/app/brep/geom/impl/line.js b/web/app/brep/geom/impl/line.js index d1963c6c..9216c81c 100644 --- a/web/app/brep/geom/impl/line.js +++ b/web/app/brep/geom/impl/line.js @@ -44,6 +44,10 @@ export class Line extends Curve { } return point; } + + translate(vector) { + return new Line(this.p0.plus(vector), this.v); + } } Line.fromTwoPlanesIntersection = function(plane1, plane2) { diff --git a/web/app/brep/geom/impl/plane.js b/web/app/brep/geom/impl/plane.js index a74c409d..a816431b 100644 --- a/web/app/brep/geom/impl/plane.js +++ b/web/app/brep/geom/impl/plane.js @@ -12,16 +12,14 @@ export class Plane extends Surface { } calculateBasis() { - const normal = this.normal; - let alignPlane, x, y; - if (Math.abs(normal.dot(AXIS.Y)) < 0.5) { - alignPlane = normal.cross(AXIS.Y); - } else { - alignPlane = normal.cross(AXIS.Z); + return BasisForPlane(this.normal); + } + + basis() { + if (!this._basis) { + this._basis = this.calculateBasis(); } - y = alignPlane.cross(normal); - x = y.cross(normal); - return [x, y, normal]; + return this._basis; } intersect(other) { @@ -30,7 +28,11 @@ export class Plane extends Surface { } return super.intersect(); } - + + translate(vector) { + return new Plane(this.normal, this.normal.dot(this.normal.multiply(this.w)._plus(vector))); + } + invert() { return new Plane(this.normal.multiply(-1), - this.w); } @@ -40,7 +42,7 @@ export class Plane extends Surface { } get3DTransformation() { - return new Matrix3().setBasis(this.calculateBasis()); + return new Matrix3().setBasis(this.basis()); } coplanarUnsigned(other, tol) { diff --git a/web/app/brep/geom/path.js b/web/app/brep/geom/path.js new file mode 100644 index 00000000..55bcd339 --- /dev/null +++ b/web/app/brep/geom/path.js @@ -0,0 +1,62 @@ +import {Point} from './point' +import {defineIterable} from '../../utils/utils' + +class Path { + + constructor(head) { + this.head = head; + defineIterable(this, 'segments', () => segmentsGenerator(this)); + } + + isClosed() { + return this.head.prev != null; + } +} + +export class PathBuilder { + constructor(head) { + this.head = null; + this.tail = null; + defineIterable(this, 'segments', () => segmentsGenerator(this)); + } + + addSegment(segment) { + if (this.tail == null) { + this.head = segment; + } else { + this.tail.next = segment; + } + segment.prev = this.tail; + this.tail = segment; + } + + close() { + this.tail.next = this.head; + this.head.prev = this.tail; + return this.head; + } + + unclosed() { + return this.head; + } + +} + +export class Segment { + constructor(curve, point) { + this.curve = curve; + this.point = point; + this.next = null; + this.prev = null; + this.data = {}; + } +} + +export function* segmentsGenerator(path) { + let node = path.head; + while (node != null) { + yield node; + node = node.next; + if (node == path.node) break; + } +} diff --git a/web/app/brep/topo/Edge.js b/web/app/brep/topo/Edge.js index 958f410d..ed4e3982 100644 --- a/web/app/brep/topo/Edge.js +++ b/web/app/brep/topo/Edge.js @@ -29,9 +29,17 @@ export class HalfEdge extends TopoObject { this.prev = null; } + static create(a, b, loop, edge) { + const e = new HalfEdge().setAB(a, b); + e.loop = loop; + e.edge = edge; + return e; + } + setAB(a, b) { this.vertexA = a; this.vertexB = b; + return this; } twin() { diff --git a/web/app/math/bezier-cubic.js b/web/app/math/bezier-cubic.js index 09d14ea9..3b6f9f75 100644 --- a/web/app/math/bezier-cubic.js +++ b/web/app/math/bezier-cubic.js @@ -3,12 +3,13 @@ import * as math from './math' export function LUT(a, b, cp1, cp2, scale) { scale = 1 / scale; - const lut = [a]; + const lut = []; for (let t = 0; t < 1; t += 0.1 * scale) { const p = compute(t, a, b, cp1, cp2); lut.push(p); } - lut.push(b); + lut[0] = a; + lut[lut.length - 1] = b; return lut; } diff --git a/web/app/math/math.js b/web/app/math/math.js index ead03abc..20f583c2 100644 --- a/web/app/math/math.js +++ b/web/app/math/math.js @@ -70,6 +70,10 @@ export function strictEqual(a, b) { return a.x == b.x && a.y == b.y && a.z == b.z; } +export function strictEqual2D(a, b) { + return a.x == b.x && a.y == b.y; +} + export function _vec(size) { var out = []; out.length = size; diff --git a/web/app/utils/utils.js b/web/app/utils/utils.js index ff8e553f..2bc9218e 100644 --- a/web/app/utils/utils.js +++ b/web/app/utils/utils.js @@ -67,6 +67,11 @@ export function camelCaseSplit(str) { return words; } +export function defineIterable(obj, name, iteratorFactory) { + obj[name] = {}; + obj[name][Symbol.iterator] = iteratorFactory; +} + export class DoubleKeyMap { constructor() { @@ -98,4 +103,12 @@ export class DoubleKeyMap { } subMap.set(b, value); } +} + +export function reversedIndex(i, n) { + let lidIdx = n - i; + if (lidIdx == n) { + lidIdx = 0; + } + return lidIdx; } \ No newline at end of file