mirror of
https://github.com/xibyte/jsketcher
synced 2025-12-24 01:15:25 +01:00
face merge / rayCast + tests
This commit is contained in:
parent
0b3939e977
commit
5c469d886b
9 changed files with 249 additions and 196 deletions
|
|
@ -242,11 +242,16 @@ export function someBasis(twoPointsOnPlane, normal) {
|
|||
}
|
||||
|
||||
export function normalOfCCWSeq(ccwSequence) {
|
||||
var a = ccwSequence[0];
|
||||
var b = ccwSequence[1];
|
||||
var c = ccwSequence[2];
|
||||
|
||||
return b.minus(a).cross(c.minus(a)).normalize();
|
||||
let a = ccwSequence[0];
|
||||
let b = ccwSequence[1];
|
||||
for (let i = 2; i < ccwSequence.length; ++i) {
|
||||
let c = ccwSequence[i];
|
||||
let normal = b.minus(a).cross(c.minus(a)).normalize();
|
||||
if (!math.equal(normal.length(), 0)) {
|
||||
return normal;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function normalOfCCWSeqTHREE(ccwSequence) {
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ function addGlobalDebugActions(app) {
|
|||
app.viewer.workGroup.add(debugGroup);
|
||||
app.viewer.workGroup.add(debugVolumeGroup);
|
||||
window.__DEBUG__ = {
|
||||
flag: 0,
|
||||
AddLine: (a, b) => {
|
||||
debugGroup.add(createLine(a, b));
|
||||
app.viewer.render();
|
||||
|
|
|
|||
|
|
@ -31,6 +31,7 @@ export default class BrepBuilder {
|
|||
this._loop = new Loop();
|
||||
this._face.innerLoops.push(this._loop);
|
||||
}
|
||||
this._loop.face = this._face;
|
||||
if (vertices) {
|
||||
for (let i = 0; i < vertices.length; ++i) {
|
||||
this.edge(vertices[i], vertices[(i + 1) % vertices.length]);
|
||||
|
|
|
|||
|
|
@ -24,6 +24,9 @@ export default class LoopDetectionExplorer extends React.PureComponent {
|
|||
|
||||
|
||||
let step = steps[this.state.step];
|
||||
if (!step) {
|
||||
return null;
|
||||
}
|
||||
let candidates = null;
|
||||
let currEdgeExplorer = null;
|
||||
if (step.type === 'NEXT_STEP_ANALYSIS') {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,11 @@
|
|||
import {BREPValidator} from '../brep-validator';
|
||||
import {Edge} from '../topo/edge';
|
||||
import {Loop} from '../topo/loop';
|
||||
import {edgesGenerator, Shell} from '../topo/shell';
|
||||
import {Shell} from '../topo/shell';
|
||||
import {Vertex} from '../topo/vertex';
|
||||
import {evolveFace} from './evolve-face'
|
||||
import PIP from '../../3d/tess/pip';
|
||||
import * as math from '../../math/math';
|
||||
import {eqEps, eqTol, eqSqTol, TOLERANCE, ueq, veq, veqNeg} from '../geom/tolerance';
|
||||
import {Ray} from "../utils/ray";
|
||||
import pickPointInside2dPolygon from "../utils/pickPointInPolygon";
|
||||
import {eqTol, TOLERANCE, ueq, veq, veqNeg} from '../geom/tolerance';
|
||||
import CadError from "../../utils/errors";
|
||||
import {createBoundingNurbs} from "../brep-builder";
|
||||
import BREP_DEBUG from '../debug/brep-debug';
|
||||
|
|
@ -26,13 +23,6 @@ const DEBUG = {
|
|||
NOOP: () => {}
|
||||
};
|
||||
|
||||
const FILTER_STRATEGIES = {
|
||||
RAY_CAST: 'RAY_CAST',
|
||||
NEW_EDGES: 'NEW_EDGES',
|
||||
};
|
||||
|
||||
const FILTER_STRATEGY = FILTER_STRATEGIES.NEW_EDGES;
|
||||
|
||||
const TYPE = {
|
||||
UNION: 'UNION',
|
||||
INTERSECT: 'INTERSECT',
|
||||
|
|
@ -128,7 +118,7 @@ export function BooleanAlgorithm( shellA, shellB, type ) {
|
|||
loopsToFaces(faceData.face, faceData.detectedLoops, faces);
|
||||
}
|
||||
|
||||
faces = filterFaces(faces, shellA, shellB, type !== TYPE.UNION);
|
||||
faces = filterFaces(faces);
|
||||
|
||||
const result = new Shell();
|
||||
faces.forEach(face => {
|
||||
|
|
@ -153,9 +143,14 @@ function removeInvalidLoops(facesData) {
|
|||
}
|
||||
}
|
||||
|
||||
function isLoopInvalid(loop) {
|
||||
//discarded by face merge routine || has reference to not reassembled loop
|
||||
return !detectedLoopsSet.has(loop);
|
||||
}
|
||||
|
||||
for (let faceData of facesData) {
|
||||
faceData.detectedLoops = faceData.detectedLoops.filter(
|
||||
loop => loop.halfEdges.find(e => !detectedLoopsSet.has(e.twin().loop)) === undefined);
|
||||
loop => loop.halfEdges.find(e => isLoopInvalid(e.twin().loop)) === undefined);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -288,19 +283,42 @@ function mergeOverlappingFaces(shellA, shellB, opType) {
|
|||
|
||||
function mergeFaces(facesA, facesB, opType) {
|
||||
let originFaces = [...facesA, ...facesB];
|
||||
let allPoints = [];
|
||||
|
||||
for (let face of originFaces) {
|
||||
face.__mergeGraph = new Map();
|
||||
for (let e of face.edges) {
|
||||
addToListInMap(face.__mergeGraph, e.vertexA, e);
|
||||
allPoints.push(e.vertexA.point);
|
||||
}
|
||||
}
|
||||
|
||||
let originFace = facesA[0];
|
||||
|
||||
let referenceSurface = createBoundingNurbs(allPoints, originFace.surface.simpleSurface);
|
||||
|
||||
let valid = new Set();
|
||||
let invalid = new Set();
|
||||
|
||||
function classify(inside, testee) {
|
||||
if (inside && opType === TYPE.INTERSECT) {
|
||||
valid.add(testee);
|
||||
return true;
|
||||
} else if (!inside && opType === TYPE.INTERSECT) {
|
||||
invalid.add(testee);
|
||||
return false;
|
||||
} else if (inside && opType === TYPE.UNION) {
|
||||
invalid.add(testee);
|
||||
return false;
|
||||
} else if (!inside && opType === TYPE.UNION) {
|
||||
valid.add(testee);
|
||||
return true;
|
||||
} else {
|
||||
throw 'invariant';
|
||||
}
|
||||
}
|
||||
|
||||
function invalidate(face, edgesIndex) {
|
||||
function invalidate(face, other) {
|
||||
let edgesIndex = other.__mergeGraph;
|
||||
for (let edge of face.edges) {
|
||||
markEdgeTransferred(edge.edge);
|
||||
let testForReversedDir = edgesIndex.get(edge.vertexB);
|
||||
|
|
@ -353,27 +371,31 @@ function mergeFaces(facesA, facesB, opType) {
|
|||
|
||||
let inside = isInsideEnclose(originFace.surface.normal(v.point),
|
||||
testee.tangentAtStart(), inEdge.tangentAtEnd(), outEdge.tangentAtStart(), true);
|
||||
|
||||
if (inside && opType === TYPE.INTERSECT) {
|
||||
valid.add(testee);
|
||||
} else if (!inside && opType === TYPE.INTERSECT) {
|
||||
invalid.add(testee);
|
||||
} else if (inside && opType === TYPE.UNION) {
|
||||
invalid.add(testee);
|
||||
} else if (!inside && opType === TYPE.UNION) {
|
||||
valid.add(testee);
|
||||
} else {
|
||||
throw 'invariant';
|
||||
}
|
||||
|
||||
classify(inside, testee);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let face1 of facesA) {
|
||||
for (let face2 of facesB) {
|
||||
invalidate(face1, face2.__mergeGraph);
|
||||
invalidate(face2, face1.__mergeGraph);
|
||||
function invalidateByRayCast(face, other) {
|
||||
for (let testee of other.edges) {
|
||||
if (!invalid.has(testee) && !valid.has(testee)) {
|
||||
let pt = testee.edge.curve.middlePoint();
|
||||
let inside = face.rayCast(pt, referenceSurface).inside;
|
||||
let isValid = classify(inside, testee);
|
||||
let classificationSet = isValid ? valid : invalid;
|
||||
for (let e of testee.loop.halfEdges) {
|
||||
classificationSet.add(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (let faceA of facesA) {
|
||||
for (let faceB of facesB) {
|
||||
invalidate(faceA, faceB);
|
||||
invalidate(faceB, faceA);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -395,6 +417,21 @@ function mergeFaces(facesA, facesB, opType) {
|
|||
}
|
||||
}
|
||||
|
||||
for (let edge of valid) {
|
||||
edge = edge.next;
|
||||
while (!valid.has(edge) && !invalid.has(edge)) {
|
||||
valid.add(edge);
|
||||
edge = edge.next;
|
||||
}
|
||||
}
|
||||
|
||||
for (let faceA of facesA) {
|
||||
for (let faceB of facesB) {
|
||||
invalidateByRayCast(faceA, faceB);
|
||||
invalidateByRayCast(faceB, faceA);
|
||||
}
|
||||
}
|
||||
|
||||
let graph = new EdgeGraph();
|
||||
let discardedEdges = new Set();
|
||||
for (let face of originFaces) {
|
||||
|
|
@ -406,17 +443,14 @@ function mergeFaces(facesA, facesB, opType) {
|
|||
}
|
||||
}
|
||||
|
||||
let allPoints = [];
|
||||
let detectedLoops = detectLoops(originFace.surface, graph);
|
||||
for (let loop of detectedLoops) {
|
||||
for (let edge of loop.halfEdges) {
|
||||
// EdgeSolveData.setPriority(edge, 1);
|
||||
EdgeSolveData.setPriority(edge, 1);
|
||||
discardedEdges.delete(edge);
|
||||
allPoints.push(edge.vertexA.point);
|
||||
}
|
||||
}
|
||||
|
||||
let referenceSurface = createBoundingNurbs(allPoints, originFace.surface.simpleSurface);
|
||||
|
||||
return {
|
||||
mergedLoops: detectedLoops,
|
||||
|
|
@ -449,137 +483,7 @@ export function mergeVertices(shell1, shell2) {
|
|||
}
|
||||
}
|
||||
|
||||
function isPointInsideSolid(pt, normal, solid) {
|
||||
let ray = new Ray(pt, normal, normal, 3000);
|
||||
for (let i = 0; i < 1; ++i) {
|
||||
let res = rayCastSolidImpl(ray, solid);
|
||||
if (res !== null) {
|
||||
return res;
|
||||
}
|
||||
ray.pertrub();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function rayCastSolidImpl(ray, solid) {
|
||||
if (DEBUG.RAY_CAST) {
|
||||
__DEBUG__.AddCurve(ray.curve, 0xffffff);
|
||||
}
|
||||
let closestDistanceSq = -1;
|
||||
let inside = null;
|
||||
let hitEdge = false;
|
||||
|
||||
let edgeDistancesSq = [];
|
||||
for (let e of solid.edges) {
|
||||
let points = e.curve.intersectCurve(ray.curve, TOLERANCE);
|
||||
for (let {p0} of points) {
|
||||
edgeDistancesSq.push(ray.pt.distanceToSquared(p0));
|
||||
}
|
||||
}
|
||||
|
||||
for (let face of solid.faces) {
|
||||
if (DEBUG.RAY_CAST) {
|
||||
__DEBUG__.AddFace(face, 0xffff00);
|
||||
}
|
||||
let pip = face.data[MY].env2D().pip;
|
||||
function isPointinsideFace(uv, pt) {
|
||||
let wpt = face.surface.createWorkingPoint(uv, pt);
|
||||
let pipClass = pip(wpt);
|
||||
return pipClass.inside;
|
||||
}
|
||||
|
||||
let originUv = face.surface.param(ray.pt);
|
||||
let originPt = face.surface.point(originUv[0], originUv[1]);
|
||||
if (eqSqTol(0, originPt.distanceToSquared(ray.pt)) && isPointinsideFace(originUv, originPt)) {
|
||||
let normal = face.surface.normalUV(originUv[0], originUv[1]);
|
||||
return normal.dot(ray.normal) > 0;
|
||||
} else {
|
||||
let uvs = face.surface.intersectWithCurve(ray.curve);
|
||||
for (let uv of uvs) {
|
||||
let normal = face.surface.normalUV(uv[0], uv[1]);
|
||||
let dotPr = normal.dot(ray.dir);
|
||||
if (eqTol(dotPr, 0)) {
|
||||
continue;
|
||||
}
|
||||
let pt = face.surface.point(uv[0], uv[1]);
|
||||
if (isPointinsideFace(uv, pt)) {
|
||||
let distSq = ray.pt.distanceToSquared(pt);
|
||||
if (closestDistanceSq === -1 || distSq < closestDistanceSq) {
|
||||
hitEdge = false;
|
||||
for (let edgeDistSq of edgeDistancesSq) {
|
||||
if (eqSqTol(edgeDistSq, distSq)) {
|
||||
hitEdge = true;
|
||||
}
|
||||
}
|
||||
closestDistanceSq = distSq;
|
||||
inside = dotPr > 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (hitEdge) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (inside === null) {
|
||||
inside = !!solid.data.inverted
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
function pickPointOnFace(face) {
|
||||
let wp = pickPointInside2dPolygon(face.createWorkingPolygon());
|
||||
if (wp === null) {
|
||||
return null;
|
||||
}
|
||||
return face.surface.workingPointTo3D(wp);
|
||||
}
|
||||
|
||||
function filterByRayCast(faces, a, b, isIntersection) {
|
||||
|
||||
let result = [];
|
||||
for (let face of faces) {
|
||||
if (DEBUG.RAY_CAST) {
|
||||
__DEBUG__.Clear();
|
||||
__DEBUG__.AddFace(face, 0x00ff00);
|
||||
}
|
||||
|
||||
let pt = pickPointOnFace(face);
|
||||
if (pt === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
let normal = face.surface.normal(pt);
|
||||
|
||||
let insideA = face.data.__origin.shell === a || isPointInsideSolid(pt, normal, a);
|
||||
let insideB = face.data.__origin.shell === b || isPointInsideSolid(pt, normal, b);
|
||||
if (isIntersection) {
|
||||
if (insideA && insideB) {
|
||||
result.push(face);
|
||||
}
|
||||
} else {
|
||||
if (insideA || insideB) {
|
||||
result.push(face);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function filterFaces(faces, a, b, isIntersection) {
|
||||
|
||||
if (FILTER_STRATEGY === FILTER_STRATEGIES.RAY_CAST) {
|
||||
return filterByRayCast(faces, a, b, isIntersection);
|
||||
} else if (FILTER_STRATEGY === FILTER_STRATEGIES.NEW_EDGES) {
|
||||
return filterFacesByNewEdges(faces);
|
||||
} else {
|
||||
throw 'unsupported';
|
||||
}
|
||||
}
|
||||
|
||||
function filterFacesByNewEdges(faces) {
|
||||
function filterFaces(faces) {
|
||||
|
||||
function doesFaceContainNewEdge(face) {
|
||||
for (let e of face.edges) {
|
||||
|
|
@ -618,7 +522,8 @@ function traverseFaces(face, callback) {
|
|||
}
|
||||
for (let loop of face.loops) {
|
||||
for (let halfEdge of loop.halfEdges) {
|
||||
stack.push(halfEdge.twin().loop.face);
|
||||
let twinFace = halfEdge.twin().loop.face;
|
||||
stack.push(twinFace);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -810,11 +715,10 @@ export function chooseValidEdge(edge, face, operationType) {
|
|||
function transferEdges(faceSource, faceDest, operationType) {
|
||||
for (let loop of faceSource.loops) {
|
||||
for (let edge of loop.halfEdges) {
|
||||
if (isEdgeTransferred(edge.edge)) {
|
||||
continue;
|
||||
}
|
||||
if (edgeCollinearToFace(edge, faceDest)) {
|
||||
if (isEdgeTransferred(edge.edge)) {
|
||||
EdgeSolveData.addPriority(edge.twin(), 1);
|
||||
continue;
|
||||
}
|
||||
let validEdge = chooseValidEdge(edge, faceDest, operationType);
|
||||
BREP_DEBUG.transferEdge(edge, faceDest, validEdge);
|
||||
let twin = validEdge.twin();
|
||||
|
|
@ -870,7 +774,7 @@ function collectNodesOfIntersection(curve, loop, nodes, operand) {
|
|||
}
|
||||
if (curve.passesThrough(v.point)) {
|
||||
let node = nodeByVertex(nodes, v, undefined, curve);
|
||||
if (isCurveEntersEnclose(curve, a, b)) {
|
||||
if (isCurveEntersEnclose(curve, a, b) === ENCLOSE_CLASSIFICATION.ENTERS) {
|
||||
node.enters[operand] = true;
|
||||
} else {
|
||||
node.leaves[operand] = true;
|
||||
|
|
@ -988,6 +892,15 @@ function splitEdgeByVertex(edge, vertex) {
|
|||
halfEdges.splice(halfEdges.indexOf(halfEdge), 1, h1, h2);
|
||||
h1.loop = halfEdge.loop;
|
||||
h2.loop = halfEdge.loop;
|
||||
|
||||
h1.prev = halfEdge.prev;
|
||||
h1.prev.next = h1;
|
||||
|
||||
h1.next = h2;
|
||||
h2.prev = h1;
|
||||
|
||||
h2.next = halfEdge.next;
|
||||
h2.next.prev = h2;
|
||||
}
|
||||
updateInLoop(edge.halfEdge1, edge1.halfEdge1, edge2.halfEdge1);
|
||||
updateInLoop(edge.halfEdge2, edge2.halfEdge2, edge1.halfEdge2);
|
||||
|
|
@ -1001,11 +914,11 @@ function splitEdgeByVertex(edge, vertex) {
|
|||
return [edge1, edge2];
|
||||
}
|
||||
|
||||
function isOnPositiveHalfPlaneFromVec(vec, testee, normal) {
|
||||
export function isOnPositiveHalfPlaneFromVec(vec, testee, normal) {
|
||||
return vec.cross(testee).dot(normal) > 0;
|
||||
}
|
||||
|
||||
function isInsideEnclose(normal, testee, inVec, outVec, strict){
|
||||
export function isInsideEnclose(normal, testee, inVec, outVec, strict){
|
||||
|
||||
if (strict && veq(outVec, testee)) {
|
||||
//TODO: improve error report
|
||||
|
|
@ -1022,7 +935,15 @@ function isInsideEnclose(normal, testee, inVec, outVec, strict){
|
|||
return testeeAngle < enclosureAngle;
|
||||
}
|
||||
|
||||
export function isCurveEntersEnclose(curve, a, b, checkCoincidence) {
|
||||
|
||||
export const ENCLOSE_CLASSIFICATION = {
|
||||
UNDEFINED: 0,
|
||||
ENTERS: 1,
|
||||
LEAVES: 2,
|
||||
TANGENTS: 3
|
||||
};
|
||||
|
||||
export function isCurveEntersEnclose(curve, a, b) {
|
||||
let pt = a.vertexB.point;
|
||||
let normal = a.loop.face.surface.normal(pt);
|
||||
|
||||
|
|
@ -1033,21 +954,34 @@ export function isCurveEntersEnclose(curve, a, b, checkCoincidence) {
|
|||
|
||||
let coiIn = veqNeg(inVec, testee);
|
||||
let coiOut = veq(outVec, testee);
|
||||
|
||||
|
||||
if (coiIn && coiOut) {
|
||||
return undefined;
|
||||
return ENCLOSE_CLASSIFICATION.UNDEFINED;
|
||||
}
|
||||
|
||||
let negated = coiIn || coiOut;
|
||||
if (negated) {
|
||||
testee = testee.negate();
|
||||
}
|
||||
let testeeNeg = testee.negate();
|
||||
|
||||
let insideEnclose = isInsideEnclose(normal, testee, inVec, outVec);
|
||||
if (negated) {
|
||||
insideEnclose = !insideEnclose;
|
||||
let coiInNeg = veqNeg(inVec, testeeNeg);
|
||||
let coiOutNeg = veq(outVec, testeeNeg);
|
||||
|
||||
if (coiInNeg || coiOutNeg) {
|
||||
return ENCLOSE_CLASSIFICATION.UNDEFINED;
|
||||
}
|
||||
return insideEnclose;
|
||||
|
||||
let result = ENCLOSE_CLASSIFICATION.UNDEFINED;
|
||||
if (coiIn || coiOut) {
|
||||
let insideEncloseNeg = isInsideEnclose(normal, testeeNeg, inVec, outVec);
|
||||
return insideEncloseNeg ? ENCLOSE_CLASSIFICATION.LEAVES : ENCLOSE_CLASSIFICATION.ENTERS;
|
||||
} else {
|
||||
let insideEnclose = isInsideEnclose(normal, testee, inVec, outVec);
|
||||
let insideEncloseNeg = isInsideEnclose(normal, testeeNeg, inVec, outVec);
|
||||
if (insideEnclose === insideEncloseNeg) {
|
||||
result = ENCLOSE_CLASSIFICATION.TANGENTS;
|
||||
} else {
|
||||
result = insideEnclose ? ENCLOSE_CLASSIFICATION.ENTERS : ENCLOSE_CLASSIFICATION.LEAVES;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
export function isCurveEntersEdgeAtPoint(curve, edge, point) {
|
||||
|
|
|
|||
|
|
@ -52,8 +52,10 @@ export class Face extends TopoObject {
|
|||
return this.getAnyHalfEdge().vertexA;
|
||||
}
|
||||
|
||||
rayCast(pt) {
|
||||
rayCast(pt, surface) {
|
||||
|
||||
surface = surface || this.surface;
|
||||
|
||||
for (let edge of this.edges) {
|
||||
if (veq(pt, edge.vertexA.point)) {
|
||||
return {
|
||||
|
|
@ -95,12 +97,12 @@ export class Face extends TopoObject {
|
|||
}
|
||||
}
|
||||
if (veq(closest.pt, closest.edge.vertexA.point)) {
|
||||
enclose = findEnclosure(closest.edge.vertexA);
|
||||
enclose = [closest.edge.prev, closest.edge, closest.edge.vertexA];
|
||||
} else if (veq(closest.pt, closest.edge.vertexB.point)) {
|
||||
enclose = findEnclosure(closest.edge.vertexB);
|
||||
enclose = [closest.edge, closest.edge.next, closest.edge.vertexB];
|
||||
}
|
||||
|
||||
let normal = this.surface.normal(closest.pt);
|
||||
let normal = surface.normal(closest.pt);
|
||||
let testee = (enclose ? enclose[2].point : closest.pt).minus(pt)._normalize();
|
||||
|
||||
// __DEBUG__.AddSegment(pt, enclose ? enclose[2].point : closest.pt);
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ function doTest(env, win, app, encA, encB, encC, curveA, curveB, expected) {
|
|||
let [a, b] = createEnclosure(app.TPI, encA, encB, encC);
|
||||
let curve = createCurve(app.TPI, curveA, curveB);
|
||||
|
||||
let result = app.TPI.brep.bool.isCurveEntersEnclose(curve, a, b);
|
||||
let result = app.TPI.brep.bool.isCurveEntersEnclose(curve, a, b) === 1;
|
||||
|
||||
draw(win, curve, a, b, result);
|
||||
env.assertTrue(result === expected);
|
||||
|
|
|
|||
106
web/test/cases/brep-raycast.js
Normal file
106
web/test/cases/brep-raycast.js
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import * as test from '../test'
|
||||
|
||||
const TESTS = {};
|
||||
let counter = 0;
|
||||
|
||||
addTest(sample1, [300, 300], true);
|
||||
addTest(sample1, [300, 200], true);
|
||||
addTest(sample1, [300, 400], false);
|
||||
addTest(sample1, [500, 500], true);
|
||||
addTest(sample1, [650, 300], false);
|
||||
addTest(sample1, [460, 280], true);
|
||||
addTest(sample1, [ 0, 100], false);
|
||||
addTest(sample1, [1000, 100], false);
|
||||
addTest(sample1, [550, 200], true);
|
||||
addTest(sample1, [730, 200], true);
|
||||
addTest(sample1, [100, 0], false);
|
||||
addTest(sample1, [300, 0], false);
|
||||
addTest(sample1, [800, 0], false);
|
||||
addTest(sample1, [850, 50], false);
|
||||
addTest(sample1, [770, 130], true);
|
||||
addTest(sample1, [800, 700], false);
|
||||
addTest(sample1, [350, 500], false);
|
||||
addTest(sample1, [300, 400], false);
|
||||
addTest(sample1, [100, 400], false);
|
||||
|
||||
addTest(sample2, [600, 100], false);
|
||||
addTest(sample2, [525, 199], false);
|
||||
addTest(sample2, [200, 140], true);
|
||||
|
||||
|
||||
function addTest(sample, pt, expected) {
|
||||
let testName = 'test' + (++counter);
|
||||
TESTS[testName] = function (env) {
|
||||
test.modeller(env.test((win, app) => {
|
||||
let face = sample(app);
|
||||
const result = rayCast(app, win, face, pt);
|
||||
env.assertTrue(expected === result.inside);
|
||||
env.done();
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function rayCast(app, win, face, pt) {
|
||||
pt = new app.TPI.brep.geom.Point().set3(pt);
|
||||
let result = face.rayCast(pt);
|
||||
win.__DEBUG__.AddFace(face);
|
||||
win.__DEBUG__.AddPoint(pt, result.inside ? 0x00ff00 : 0xff0000);
|
||||
return result;
|
||||
}
|
||||
|
||||
function sample1(app) {
|
||||
return createFace(app.TPI,[
|
||||
[500, 300],
|
||||
[300, 300],
|
||||
[100, 300],
|
||||
[100, 100],
|
||||
[300, 100],
|
||||
[500, 100],
|
||||
[800, 100],
|
||||
[800, 600],
|
||||
[500, 600],
|
||||
[400, 500],
|
||||
[500, 400],
|
||||
], [[
|
||||
[600, 500],
|
||||
[700, 500],
|
||||
[700, 200],
|
||||
[600, 200],
|
||||
]]);
|
||||
|
||||
}
|
||||
|
||||
function sample2(app) {
|
||||
return createFace(app.TPI,[
|
||||
[500, 100],
|
||||
[100, 200],
|
||||
[100, 100]
|
||||
], []);
|
||||
}
|
||||
|
||||
function createFace(tpi, _outer, _holes) {
|
||||
|
||||
const bb = new tpi.brep.builder();
|
||||
const vx = p => bb.vertex(p[0], p[1], 0);
|
||||
|
||||
let outer = _outer.map(vx);
|
||||
let holes = _holes.map(h => h.map(vx));
|
||||
|
||||
let face1 = bb.face();
|
||||
face1.loop(outer);
|
||||
for (let hole of holes) {
|
||||
face1.loop(hole);
|
||||
}
|
||||
|
||||
let face2 = bb.face();
|
||||
outer.reverse();
|
||||
face2.loop(outer);
|
||||
for (let hole of holes) {
|
||||
hole.reverse();
|
||||
face2.loop(hole);
|
||||
}
|
||||
return bb.build().faces[0];
|
||||
}
|
||||
|
||||
export default TESTS;
|
||||
|
|
@ -27,6 +27,7 @@ export default {
|
|||
TestCase('brep-bool'),
|
||||
TestCase('brep-bool-wizard-based'),
|
||||
TestCase('brep-pip'),
|
||||
TestCase('brep-raycast'),
|
||||
TestCase('brep-enclose')
|
||||
],
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue