PIP for NURBS

This commit is contained in:
Val Erastov 2017-04-13 17:49:03 -07:00 committed by xibyte
parent cad474b100
commit 13f2f8f6ee
11 changed files with 415 additions and 78 deletions

View file

@ -24,9 +24,14 @@ function addGlobalDebugActions(app) {
app.viewer.render();
},
AddSegment: (a, b, color) => {
debugGroup.add(createLine(a, b, color));
debugGroup.add(createPoint(a, 0x000088));
debugGroup.add(createPoint(b, 0x880000));
__DEBUG__.AddPolyLine([a, b], color);
},
AddPolyLine: (points, color) => {
for (let i = 1; i < points.length; ++i) {
debugGroup.add(createLine(points[i - 1], points[i], color));
}
debugGroup.add(createPoint(points[0], 0x000088));
debugGroup.add(createPoint(points[points.length - 1], 0x880000));
app.viewer.render();
},
AddPoint: (coordinates, or, vector, andColorAtTheEnd) => {
@ -55,7 +60,12 @@ function addGlobalDebugActions(app) {
app.viewer.render();
},
AddHalfEdge: (he, color) => {
window.__DEBUG__.AddSegment(he.vertexA.point, he.vertexB.point, color);
const points = [he.vertexA.point];
if (he.edge && he.edge.curve) {
he.edge.curve.approximate(10, he.vertexA.point, he.vertexB.point, points);
}
points.push(he.vertexB.point);
window.__DEBUG__.AddPolyLine(points, color);
},
AddFace: (face, color) => {
for (let e of face.edges) __DEBUG__.AddHalfEdge(e, color);

View file

@ -149,6 +149,30 @@ export function triangulateToThree(shell, geom) {
}
//view.setFaceColor(sceneFace, utils.isSmoothPiece(group.shared) ? 0xFF0000 : null);
}
} else if (brepFace.surface.constructor.name == 'NurbsSurface1') {
const off = geom.vertices.length;
const contours = [];
for (let loop of brepFace.loops) {
const points = [];
for (let he of loop.halfEdges) {
points.push(he.vertexA.point);
he.edge.curve.approximate(10, he.vertexA.point, he.vertexB.point, points);
}
const verb = brepFace.surface.verb;
const uvs = points.map(point => verb.closestParam(point.data()));
uvs.forEach(uv => uv.push(0)); // add z coord
contours.push(uvs);
//....TODO
for (let i = 0; i < tessedUVs.length; i += 3 ) {
var a = new Vector().set3(tessedUVs[i]);
var b = new Vector().set3(tessedUVs[i + 1]);
var c = new Vector().set3(tessedUVs[i + 2]);
const normalOrNormals = normalOfCCWSeq([a, b, c]).three();
const face = new THREE.Face3(off, off + 1, off + 2, normalOrNormals);
addFace(face);
}
}
} else if (brepFace.surface.constructor.name == 'NurbsSurface') {
const off = geom.vertices.length;
const tess = brepFace.surface.verb.tessellate({maxDepth: 3});

View file

@ -9,6 +9,8 @@ import {Face} from '../brep/topo/face';
import {Shell} from '../brep/topo/shell';
import {Vertex} from '../brep/topo/vertex';
import {Point} from '../brep/geom/point';
import {NurbsCurve} from '../brep/geom/impl/nurbs';
import {Plane} from '../brep/geom/impl/plane';
export default {
brep: {
@ -17,7 +19,7 @@ export default {
bool: BREPBool,
validator: BREPValidator,
geom: {
Point
Point, NurbsCurve, Plane
},
topo: {
HalfEdge, Edge, Loop, Face, Shell, Vertex

View file

@ -186,12 +186,14 @@ export function invertLoop(loop) {
linkSegments(loop.halfEdges);
}
export function createPlaneLoop(vertices) {
export function createPlaneLoop(vertices, curves) {
const loop = new Loop();
iterateSegments(vertices, (a, b) => {
createHalfEdge(loop, a, b)
iterateSegments(vertices, (a, b, i) => {
const halfEdge = createHalfEdge(loop, a, b);
halfEdge.edge = new Edge(curves[i] ? curves[i] : Line.fromSegment(a.point, b.point));
return halfEdge;
});
linkSegments(loop.halfEdges);

View file

@ -2,7 +2,6 @@
export class Curve {
constructor() {
this.isLine = false;
}
intersectCurve(curve) {
@ -20,7 +19,7 @@ export class Curve {
approximate(resolution, from, to, path) {
}
}
Curve.prototype.isLine = false;
export class CompositeCurve {

View file

@ -4,7 +4,6 @@ export class Line extends Curve {
constructor(p0, v) {
super();
this.isLine = true;
this.p0 = p0;
this.v = v;
this._pointsCache = new Map();
@ -56,6 +55,8 @@ export class Line extends Curve {
offset() {};
}
Line.prototype.isLine = true;
Line.fromTwoPlanesIntersection = function(plane1, plane2) {
const n1 = plane1.normal;
const n2 = plane2.normal;

View file

@ -1,6 +1,7 @@
import verb from 'verb-nurbs'
import {Matrix3} from '../../../math/l3space'
import Vector from '../../../math/vector'
import * as math from '../../../math/math'
import {Point} from '../point'
export class NurbsCurve {
@ -31,7 +32,7 @@ export class NurbsCurve {
const step = this.verb.paramAtLength(length / resolution);
u += step;
for (;u < endU; u += step) {
out.push(new Vector().set3(this.verb.point(u)));
out.push(new Point().set3(this.verb.point(u)));
}
if (reverse) {
for (let i = off, j = out.length - 1; i != j; ++i, --j) {
@ -41,8 +42,48 @@ export class NurbsCurve {
}
}
}
approximateU(resolution, paramFrom, paramTo, consumer) {
let u = paramFrom;
let endU = paramTo;
let step = this.verb.paramAtLength(resolution);
if (u > endU) {
step *= -1;
}
u += step;
for (;step > 0 ? u < endU : u > endU; u += step) {
consumer(u);
}
}
tangentAtPoint(point) {
return new Point().set3(this.verb.tangent(this.verb.closestParam(point.data())));
}
closestDistanceToPoint(point) {
const closest = this.verb.closestPoint(point.data());
return math.distance3(point.x, point.y, point.z, closest[0], closest[1], closest[2]);
}
tangent(point) {
return new Point().set3(this.verb.tangent( this.verb.closestParam(point.data() )));
}
intersect(other, tolerance) {
return verb.geom.Intersect.curves(this.verb, other.verb, tolerance).map(i => new Point().set3(i.point0));
}
static createByPoints(points, degeree) {
points = points.map(p => p.data());
return new NurbsCurve(new verb.geom.NurbsCurve.byPoints(points, degeree));
}
}
NurbsCurve.prototype.createLinearNurbs = function(a, b) {
return new NurbsCurve(new verb.geom.Line(a.data(), b.data()));
};
export class NurbsSurface {
constructor(verbSurface) {

View file

@ -1,4 +1,5 @@
import {Surface} from '../surface'
import {Point} from '../point'
import {Line} from './line'
import {Matrix3, AXIS, BasisForPlane} from '../../../math/l3space'
import * as math from '../../../math/math'
@ -38,11 +39,17 @@ export class Plane extends Surface {
}
get2DTransformation() {
return this.get3DTransformation().invert();
if (!this.__2dTr) {
this.__2dTr = this.get3DTransformation().invert();
}
return this.__2dTr;
}
get3DTransformation() {
return new Matrix3().setBasis(this.basis());
if (!this.__3dTr) {
this.__3dTr = new Matrix3().setBasis(this.basis());
}
return this.__3dTr;
}
coplanarUnsigned(other, tol) {
@ -58,8 +65,27 @@ export class Plane extends Surface {
}
toParametricForm() {
const basis = BasisForPlane(this.normal);
return new ParametricPlane(this.normal.multiply(this.w), basis.x, basis.y);
if (!this.__parametricForm) {
const basis = BasisForPlane(this.normal);
this.__parametricForm = new ParametricPlane(this.normal.multiply(this.w), basis.x, basis.y);
}
return this.__parametricForm;
}
toUV(point) {
return this.get2DTransformation().apply(point);
}
fromUV(u, v) {
return this.get3DTransformation()._apply(new Point(u, v, 0));
}
domainU() {
return [Number.MIN_VALUE, Number.MAX_VALUE];
}
domainV() {
return [Number.MIN_VALUE, Number.MAX_VALUE];
}
}

View file

@ -667,7 +667,6 @@ export function loopsToFaces(originFace, loops, out) {
}
function getNestedLoops(face, brepLoops) {
const tr = face.surface.get2DTransformation();
function NestedLoop(loop) {
this.loop = loop;
this.nesting = [];
@ -677,7 +676,7 @@ function getNestedLoops(face, brepLoops) {
const loops = brepLoops.map(loop => new NestedLoop(loop));
function contains(loop, other) {
for (let point of other.asPolygon()) {
if (!classifyPointInsideLoop(tr.apply(point), loop, tr).inside) {
if (!classifyPointInsideLoop(point, loop, face.surface).inside) {
return false;
}
}
@ -1076,9 +1075,9 @@ function classifyPointToFace(point, face) {
}
return result;
}
const tr = face.surface.get2DTransformation();
const point2d = tr.apply(point);
const outer = classifyPointInsideLoop(point2d, face.outerLoop, tr);
const uvPt = face.surface.toUV(point);
const outer = classifyPointInsideLoop(point, face.outerLoop, face.surface, uvPt);
if (outer.inside) {
if (outer.vertex || outer.edge) {
@ -1087,7 +1086,7 @@ function classifyPointToFace(point, face) {
}
for (let innerLoop of face.innerLoops) {
const inner = classifyPointInsideLoop(point2d, innerLoop, tr);
const inner = classifyPointInsideLoop(point, innerLoop, face.surface, uvPt);
if (inner.vertex || inner.edge) {
return inner;
}
@ -1209,7 +1208,7 @@ class FaceSolveData {
}
}
export function classifyPointInsideLoop( inPt, loop, tr ) {
export function classifyPointInsideLoop( pt, loop, surface, uvPt ) {
function VertexResult(vertex) {
this.inside = true;
@ -1221,20 +1220,37 @@ export function classifyPointInsideLoop( inPt, loop, tr ) {
this.edge = edge;
}
const _2dCoords = new Map();
if (!uvPt) {
uvPt = surface.toUV(pt);
}
function isLine(edge) {
return !edge.edge || !edge.edge.curve || edge.edge.curve.isLine;
}
const uvCoords = new Map();
for( let edge of loop.halfEdges ) {
const p = tr.apply(edge.vertexA.point);
if (math.areEqual(inPt.y, p.y, TOLERANCE) && math.areEqual(inPt.x, p.x, TOLERANCE)) {
const uv = surface.toUV(edge.vertexA.point);
if (math.areEqual(uvPt.y, uv.y, TOLERANCE) && math.areEqual(uvPt.x, uv.x, TOLERANCE)) {
return new VertexResult(edge.vertexA);
}
_2dCoords.set(edge.vertexA, p);
uvCoords.set(edge.vertexA, uv);
}
const grads = [];
for( let edge of loop.halfEdges ) {
const a = _2dCoords.get(edge.vertexA);
const b = _2dCoords.get(edge.vertexB);
const dy = b.y - a.y;
const a = uvCoords.get(edge.vertexA);
const b = uvCoords.get(edge.vertexB);
let dy;
if (isLine(edge)) {
dy = b.y - a.y;
} else {
const tangent = edge.edge.curve.tangent(edge.vertexA.point);
dy = surface.toUV(tangent).y;
if (edge.edge.invertedToCurve) {
dy *= -1;
}
}
if (math.areEqual(dy, 0, TOLERANCE)) {
grads.push(0)
} else if (dy > 0) {
@ -1264,6 +1280,7 @@ export function classifyPointInsideLoop( inPt, loop, tr ) {
const skip = new Set();
let ray = null;
let inside = false;
for( let i = 0; i < loop.halfEdges.length; ++i) {
@ -1271,11 +1288,11 @@ export function classifyPointInsideLoop( inPt, loop, tr ) {
var shouldBeSkipped = skip.has(edge.vertexA) || skip.has(edge.vertexB);
const a = _2dCoords.get(edge.vertexA);
const b = _2dCoords.get(edge.vertexB);
const a = uvCoords.get(edge.vertexA);
const b = uvCoords.get(edge.vertexB);
const aEq = math.areEqual(inPt.y, a.y, TOLERANCE);
const bEq = math.areEqual(inPt.y, b.y, TOLERANCE);
const aEq = math.areEqual(uvPt.y, a.y, TOLERANCE);
const bEq = math.areEqual(uvPt.y, b.y, TOLERANCE);
if (aEq) {
skip.add(edge.vertexA);
@ -1287,50 +1304,98 @@ export function classifyPointInsideLoop( inPt, loop, tr ) {
if (math.areVectorsEqual(a, b, TOLERANCE)) {
console.error('unable to classify invalid polygon');
}
var edgeLowPt = a;
var edgeHighPt = b;
var edgeDx = edgeHighPt.x - edgeLowPt.x;
var edgeDy = edgeHighPt.y - edgeLowPt.y;
if (aEq && bEq) {
if ( ( ( edgeHighPt.x <= inPt.x ) && ( inPt.x <= edgeLowPt.x ) ) ||
( ( edgeLowPt.x <= inPt.x ) && ( inPt.x <= edgeHighPt.x ) ) ) {
return new EdgeResult(edge);
} else {
if (isLine(edge)) {
let edgeLowPt = a;
let edgeHighPt = b;
let edgeDx = edgeHighPt.x - edgeLowPt.x;
let edgeDy = edgeHighPt.y - edgeLowPt.y;
if (aEq && bEq) {
if ( ( ( edgeHighPt.x <= uvPt.x ) && ( uvPt.x <= edgeLowPt.x ) ) ||
( ( edgeLowPt.x <= uvPt.x ) && ( uvPt.x <= edgeHighPt.x ) ) ) {
return new EdgeResult(edge);
} else {
continue;
}
}
if (shouldBeSkipped) {
continue;
}
}
if (shouldBeSkipped) {
continue;
}
if ( edgeDy < 0 ) {
edgeLowPt = b; edgeDx = - edgeDx;
edgeHighPt = a; edgeDy = - edgeDy;
}
if (!aEq && !bEq && ( inPt.y < edgeLowPt.y || inPt.y > edgeHighPt.y ) ) {
continue;
}
if (bEq) {
if (grads[i] * nextGrad(i) < 0) {
if ( edgeDy < 0 ) {
edgeLowPt = b; edgeDx = - edgeDx;
edgeHighPt = a; edgeDy = - edgeDy;
}
if (!aEq && !bEq && ( uvPt.y < edgeLowPt.y || uvPt.y > edgeHighPt.y ) ) {
continue;
}
} else if (aEq) {
if (grads[i] * prevGrad(i) < 0) {
if (bEq) {
if (grads[i] * nextGrad(i) < 0) {
continue;
}
} else if (aEq) {
if (grads[i] * prevGrad(i) < 0) {
continue;
}
}
let perpEdge = edgeDx * (uvPt.y - edgeLowPt.y) - edgeDy * (uvPt.x - edgeLowPt.x);
if ( math.areEqual(perpEdge, 0, TOLERANCE) ) return new EdgeResult(edge); // uvPt is on contour ?
if ( perpEdge < 0 ) {
continue;
}
}
inside = ! inside; // true intersection left of uvPt
} else {
if (aEq && bEq) {
if (math.areEqual(edge.edge.curve.closestDistanceToPoint(pt), 0, TOLERANCE)) {
return new EdgeResult(edge);
} else {
continue;
}
}
let perpEdge = edgeDx * (inPt.y - edgeLowPt.y) - edgeDy * (inPt.x - edgeLowPt.x);
if ( math.areEqual(perpEdge, 0, TOLERANCE) ) return new EdgeResult(edge); // inPt is on contour ?
if ( perpEdge < 0 ) {
continue;
if (shouldBeSkipped) {
continue;
}
if (bEq) {
if (grads[i] * nextGrad(i) < 0) {
continue;
}
} else if (aEq) {
if (grads[i] * prevGrad(i) < 0) {
continue;
}
}
if (math.areEqual(edge.edge.curve.closestDistanceToPoint(pt), 0, TOLERANCE)) {
return new EdgeResult(edge);
}
if (ray == null) {
let rayEnd = pt.copy();
//fixme!!
rayEnd.x = 1000000;//surface.fromUV(surface.domainU()[1]).x;
ray = edge.edge.curve.createLinearNurbs(pt, rayEnd);
}
const hits = edge.edge.curve.intersect(ray);
for (let hit of hits) {
//if ray just touches
const onlyTouches = math.areEqual(edge.edge.curve.tangent(hit).normalize().y, 0, TOLERANCE);
if (!onlyTouches) {
inside = ! inside;
}
}
}
inside = ! inside; // true intersection left of inPt
}
return {inside};

View file

@ -15,9 +15,9 @@ Vector.prototype.set = function(x, y, z) {
};
Vector.prototype.set3 = function(data) {
this.x = data[0];
this.y = data[1];
this.z = data[2];
this.x = data[0] || 0;
this.y = data[1] || 0;
this.z = data[2] || 0;
return this;
};

View file

@ -1,5 +1,5 @@
import * as test from '../test'
import {Matrix3} from '../../app/math/l3space'
import {AXIS} from '../../app/math/l3space'
export default {
@ -639,6 +639,167 @@ export default {
}));
},
/**
* o--------o
* | \
* *--> | )
* | /
* o--------o
*/
testPIPClassification1NurbsOut: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,['nurbs', [500, 100], [700, 250], [500, 500] ]]);
const result = classify(app, win, loop, [-300, 300]);
env.assertFalse(result.inside);
env.done();
}));
},
/**
* o--------o
* | \
* *--> | )
* | /
* o--------o
*/
testPIPClassification1NurbsIn: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,['nurbs', [500, 100], [700, 250], [500, 500] ]]);
const result = classify(app, win, loop, [300, 300]);
env.assertTrue(result.inside);
env.done();
}));
},
/**
* *-->
*
* . ' .
* / \
* o o
* | |
* | |
* | |
* o--------o
*/
testPIPClassificationCloseToNurbsOut: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,,['nurbs', [500, 500], [250, 750], [100, 500] ]]);
const result = classify(app, win, loop, [-300, 780]);
env.assertFalse(result.inside);
env.done();
}));
},
/**
* *-->
* . ' .
* / \
* o o
* | |
* | |
* | |
* o--------o
*/
testPIPClassificationTouchesNurbsOut: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,,['nurbs', [500, 500], [250, 750], [100, 500] ]]);
const result = classify(app, win, loop, [-300, 750]);
env.assertFalse(result.inside);
env.done();
}));
},
/**
*
* *--> . ' .
* / \
* o o
* | |
* | |
* | |
* o--------o
*/
testPIPClassificationThroughNurbsOut: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,,['nurbs', [500, 500], [250, 750], [100, 500] ]]);
const result = classify(app, win, loop, [-300, 650]);
env.assertFalse(result.inside);
env.done();
}));
},
/**
*
* . ' .
* / *-> \
* o o
* | |
* | |
* | |
* o--------o
*/
testPIPClassificationCrossesNurbsIn: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,,['nurbs', [500, 500], [250, 750], [100, 500] ]]);
const result = classify(app, win, loop, [300, 650]);
env.assertTrue(result.inside);
env.done();
}));
},
/**
* o--------o
* \ \
* *--> ) )
* / /
* o--------o
*/
testPIPClassification2NurbsOut: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
[100, 100],
[500, 100],
[500, 500],
[100, 500]
], [,['nurbs', [500, 100], [700, 250], [500, 500]], , ['nurbs', [100, 500], [250, 250], [100, 100]]]);
const result = classify(app, win, loop, [-300, 300]);
env.assertFalse(result.inside);
env.done();
}));
},
testPIPClassification_TR_OUT_TR_INNER: function (env) {
test.modeller(env.test((win, app) => {
const loop = createLoop(app.TPI,[
@ -660,14 +821,13 @@ export default {
}
function classify(app, win, loop, p) {
const IDENTITY = new Matrix3();
loop.halfEdges.forEach(e => win.__DEBUG__.AddHalfEdge(e, 0xffff00));
const pnt = point(app.TPI, p[0], p[1], 0);
const beam = pnt.copy();
beam.x += 1700;
win.__DEBUG__.AddLine(pnt, beam);
win.__DEBUG__.AddPoint(pnt, 0xffffff);
const result = app.TPI.brep.bool.classifyPointInsideLoop(pnt, loop, IDENTITY);
const result = app.TPI.brep.bool.classifyPointInsideLoop(pnt, loop, new app.TPI.brep.geom.Plane(AXIS.Z, 0));
win.__DEBUG__.AddPoint(pnt, result.inside ? 0x00ff00 : 0xff0000);
if (result.edge) {
win.__DEBUG__.AddHalfEdge(result.edge, 0xffffff)
@ -676,9 +836,16 @@ function classify(app, win, loop, p) {
}
function createLoop(tpi, points) {
function createLoop(tpi, points, curves) {
curves = curves || [];
const vertices = points.map(p => vertex(tpi, p[0], p[1], 0));
return tpi.brep.builder.createPlaneLoop(vertices);
return tpi.brep.builder.createPlaneLoop(vertices, curves.map(c => {
if (c && c[0] == 'nurbs') {
const points = c.slice(1).map(p => new tpi.brep.geom.Point().set3(p) );
return tpi.brep.geom.NurbsCurve.createByPoints(points, 2);
}
return undefined;
}));
}
function vertex(tpi, x, y, z) {