Skip to content

Commit

Permalink
Merged PR 86069: Area booleans for Loop and ParityRegion objects.
Browse files Browse the repository at this point in the history
* (new) static RegionOps.regionBooleanXY - boolean operations among regions with curved boundaries. (Major feature)

* Supporting changes
  * (new) enum RegionBinaryOpType {Union, Intersection, Parity, AMinusB, BMinusA.
  * curveCollection.cyclicCurvePrimitive -- optional second argument to suppress cyclic behavior.
  * curveChain.primitiveIndex to CurveLocationDetailPointAndDerivative
  * static CurveLocationDetail.createCurveEvaluatedFractionPointAndDerivative
  * CurveCollection and CurvePrimitive - implementations of collectCurvePrimitives and collectCurvePrimitivesGo have
   additional optional argument to request exploding linestrings to line segments.
  • Loading branch information
EarlinLutz committed May 11, 2020
1 parent 15a22d7 commit b99a594
Show file tree
Hide file tree
Showing 22 changed files with 1,185 additions and 422 deletions.
30 changes: 25 additions & 5 deletions common/api/geometry-core.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -1259,10 +1259,11 @@ export abstract class CurveChain extends CurveCollection {
get children(): CurvePrimitive[];
abstract cloneStroked(options?: StrokeOptions): AnyCurve;
protected _curves: CurvePrimitive[];
cyclicCurvePrimitive(index: number): CurvePrimitive | undefined;
cyclicCurvePrimitive(index: number, cyclic?: boolean): CurvePrimitive | undefined;
extendRange(range: Range3d, transform?: Transform): void;
getChild(i: number): CurvePrimitive | undefined;
getPackedStrokes(options?: StrokeOptions): GrowableXYZArray | undefined;
primitiveIndexAndFractionToCurveLocationDetailPointAndDerivative(index: number, fraction: number, cyclic?: boolean, result?: CurveLocationDetail): CurveLocationDetail | undefined;
reverseChildrenInPlace(): void;
tryAddChild(child: AnyCurve | undefined): boolean;
}
Expand Down Expand Up @@ -1319,7 +1320,7 @@ export abstract class CurveCollection extends GeometryQuery {
abstract cloneStroked(options?: StrokeOptions): AnyCurve;
cloneTransformed(transform: Transform): CurveCollection | undefined;
cloneWithExpandedLineStrings(): CurveCollection | undefined;
collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean): CurvePrimitive[];
collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean, explodeLineStrings?: boolean): CurvePrimitive[];
static createCurveLocationDetailOnAnyCurvePrimitive(source: GeometryQuery | undefined, fraction?: number): CurveLocationDetail | undefined;
abstract readonly curveCollectionType: CurveCollectionType;
abstract dgnBoundaryType(): number;
Expand Down Expand Up @@ -1434,6 +1435,7 @@ export class CurveLocationDetail {
static createConditionalMoveSignedDistance(allowExtension: boolean, curve: CurvePrimitive, startFraction: number, endFraction: number, requestedSignedDistance: number, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveEvaluatedFraction(curve: CurvePrimitive, fraction: number, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveEvaluatedFractionFraction(curve: CurvePrimitive, fraction0: number, fraction1: number, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveEvaluatedFractionPointAndDerivative(curve: CurvePrimitive, fraction: number, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveFractionPoint(curve: CurvePrimitive | undefined, fraction: number, point: Point3d, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveFractionPointDistance(curve: CurvePrimitive, fraction: number, point: Point3d, a: number, result?: CurveLocationDetail): CurveLocationDetail;
static createCurveFractionPointDistanceCurveSearchStatus(curve: CurvePrimitive | undefined, fraction: number, point: Point3d, distance: number, status: CurveSearchStatus, result?: CurveLocationDetail): CurveLocationDetail;
Expand Down Expand Up @@ -1486,8 +1488,8 @@ export abstract class CurvePrimitive extends GeometryQuery {
appendPlaneIntersectionPoints(plane: PlaneAltitudeEvaluator, result: CurveLocationDetail[]): number;
clonePartialCurve(_fractionA: number, _fractionB: number): CurvePrimitive | undefined;
closestPoint(spacePoint: Point3d, extend: VariantCurveExtendParameter): CurveLocationDetail | undefined;
collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean): CurvePrimitive[];
collectCurvePrimitivesGo(collectorArray: CurvePrimitive[], _smallestPossiblePrimitives: boolean): void;
collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean, explodeLinestrings?: boolean): CurvePrimitive[];
collectCurvePrimitivesGo(collectorArray: CurvePrimitive[], _smallestPossiblePrimitives: boolean, _explodeLinestrings?: boolean): void;
computeAndAttachRecursiveStrokeCounts(options?: StrokeOptions, parentMap?: StrokeCountMap): void;
abstract computeStrokeCountForOptions(options?: StrokeOptions): number;
curveLength(): number;
Expand All @@ -1512,6 +1514,7 @@ export abstract class CurvePrimitive extends GeometryQuery {
abstract isInPlane(plane: Plane3dByOriginAndUnitNormal): boolean;
moveSignedDistanceFromFraction(startFraction: number, signedDistance: number, allowExtension: boolean, result?: CurveLocationDetail): CurveLocationDetail;
protected moveSignedDistanceFromFractionGeneric(startFraction: number, signedDistance: number, allowExtension: boolean, result?: CurveLocationDetail): CurveLocationDetail;
parent?: any;
abstract quickLength(): number;
abstract reverseInPlace(): void;
// @internal
Expand Down Expand Up @@ -2734,6 +2737,7 @@ export class LineString3d extends CurvePrimitive implements BeJSONFunctions {
clonePartialCurve(fractionA: number, fractionB: number): CurvePrimitive | undefined;
cloneTransformed(transform: Transform): CurvePrimitive;
closestPoint(spacePoint: Point3d, extend: VariantCurveExtendParameter, result?: CurveLocationDetail): CurveLocationDetail;
collectCurvePrimitivesGo(collectorArray: CurvePrimitive[], _smallestPossiblePrimitives: boolean, explodeLinestrings?: boolean): void;
computeAndAttachRecursiveStrokeCounts(options?: StrokeOptions, parentStrokeMap?: StrokeCountMap): void;
computeStrokeCountForOptions(options?: StrokeOptions): number;
computeUVFromXYZTransform(transform: Transform): void;
Expand Down Expand Up @@ -4390,6 +4394,20 @@ export abstract class RecursiveCurveProcessorWithStack extends RecursiveCurvePro
protected _stack: CurveCollection[];
}

// @alpha
export enum RegionBinaryOpType {
// (undocumented)
AMinusB = 3,
// (undocumented)
BMinusA = 4,
// (undocumented)
Intersection = 2,
// (undocumented)
Parity = 1,
// (undocumented)
Union = 0
}

// @internal
export class RegionMomentsXY extends NullGeometryHandler {
handleArc3d(arc: Arc3d): void;
Expand All @@ -4412,7 +4430,7 @@ export class RegionOps {
static addLoopsWithEdgeTagToGraph(graph: HalfEdgeGraph, data: MultiLineStringDataVariant, mask: HalfEdgeMask, edgeTag: any): HalfEdge[] | undefined;
static cloneCurvesWithXYSplitFlags(curvesToCut: CurvePrimitive | CurveCollection | undefined, cutterCurves: CurveCollection): CurveCollection | CurvePrimitive | undefined;
static collectChains(fragments: GeometryQuery[], gapTolerance: number): ChainTypes;
static collectCurvePrimitives(candidates: AnyCurve | AnyCurve[], collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean): CurvePrimitive[];
static collectCurvePrimitives(candidates: AnyCurve | AnyCurve[], collectorArray?: CurvePrimitive[], smallestPossiblePrimitives?: boolean, explodeLinestrings?: boolean): CurvePrimitive[];
static collectInsideAndOutsideOffsets(fragments: GeometryQuery[], offsetDistance: number, gapTolerance: number): {
insideOffsets: GeometryQuery[];
outsideOffsets: GeometryQuery[];
Expand All @@ -4433,6 +4451,8 @@ export class RegionOps {
static polygonXYAreaIntersectLoopsToPolyface(loopsA: MultiLineStringDataVariant, loopsB: MultiLineStringDataVariant, triangulate?: boolean): Polyface | undefined;
static polygonXYAreaUnionLoopsToPolyface(loopsA: MultiLineStringDataVariant, loopsB: MultiLineStringDataVariant, triangulate?: boolean): Polyface | undefined;
static rectangleEdgeTransform(data: AnyCurve | Point3d[] | IndexedXYZCollection, requireClosurePoint?: boolean): Transform | undefined;
// @alpha
static regionBooleanXY(loopsA: AnyRegion | AnyRegion[] | undefined, loopsB: AnyRegion | AnyRegion[] | undefined, operation: RegionBinaryOpType): AnyRegion | undefined;
// @internal
static setCheckPointFunction(f?: GraphCheckPointFunction): void;
static sortOuterAndHoleLoopsXY(loops: Array<Loop | IndexedXYZCollection>): AnyRegion;
Expand Down
1 change: 1 addition & 0 deletions common/api/summary/geometry-core.exports.csv
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ public;Ray3d
public;RecurseToCurvesGeometryHandler
public;class RecursiveCurveProcessor
public;class RecursiveCurveProcessorWithStack
alpha;RegionBinaryOpType
internal;RegionMomentsXY
beta;RegionOps
public;RotationalSweep
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@bentley/geometry-core",
"comment": "(alpha) boolean operations among regions with curved boundaries.",
"type": "none"
}
],
"packageName": "@bentley/geometry-core",
"email": "[email protected]"
}
83 changes: 47 additions & 36 deletions core/geometry/src/curve/CurveCollection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,28 @@
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/

/** @packageDocumentation
* @module Curve
*/
import { Geometry } from "../Geometry";
import { GeometryHandler } from "../geometry3d/GeometryHandler";
import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray";
import { Range3d } from "../geometry3d/Range";
import { Transform } from "../geometry3d/Transform";
import { AnyCurve } from "./CurveChain";
import { CurveLocationDetail } from "./CurveLocationDetail";
import { CurvePrimitive } from "./CurvePrimitive";
import { RecursiveCurveProcessor } from "./CurveProcessor";
import { GeometryQuery } from "./GeometryQuery";
import { CloneCurvesContext } from "./internalContexts/CloneCurvesContext";
import { CloneWithExpandedLineStrings } from "./internalContexts/CloneWithExpandedLineStrings";
import { CountLinearPartsSearchContext } from "./internalContexts/CountLinearPartsSearchContext";
import { GapSearchContext } from "./internalContexts/GapSearchContext";
import { SumLengthsContext } from "./internalContexts/SumLengthsContext";
import { TransformInPlaceContext } from "./internalContexts/TransformInPlaceContext";
import { LineString3d } from "./LineString3d";
import { StrokeOptions } from "./StrokeOptions";

/** @packageDocumentation
* @module Curve
*/
import { Geometry } from "../Geometry";
import { GeometryHandler } from "../geometry3d/GeometryHandler";
import { GrowableXYZArray } from "../geometry3d/GrowableXYZArray";
import { Range3d } from "../geometry3d/Range";
import { Transform } from "../geometry3d/Transform";
import { AnyCurve } from "./CurveChain";
import { CurveLocationDetail } from "./CurveLocationDetail";
import { CurvePrimitive } from "./CurvePrimitive";
import { RecursiveCurveProcessor } from "./CurveProcessor";
import { GeometryQuery } from "./GeometryQuery";
import { CloneCurvesContext } from "./internalContexts/CloneCurvesContext";
import { CloneWithExpandedLineStrings } from "./internalContexts/CloneWithExpandedLineStrings";
import { CountLinearPartsSearchContext } from "./internalContexts/CountLinearPartsSearchContext";
import { GapSearchContext } from "./internalContexts/GapSearchContext";
import { SumLengthsContext } from "./internalContexts/SumLengthsContext";
import { TransformInPlaceContext } from "./internalContexts/TransformInPlaceContext";
import { LineString3d } from "./LineString3d";
import { StrokeOptions } from "./StrokeOptions";

/** Describes the concrete type of a [[CurveCollection]]. Each type name maps to a specific subclass and can be used in conditional statements for type-switching.
* - "loop" => [[Loop]]
* - "path" => [[Path]]
Expand Down Expand Up @@ -80,13 +80,13 @@ export abstract class CurveCollection extends GeometryQuery {
return CloneWithExpandedLineStrings.clone(this);
}
/** Recurse through children to collect CurvePrimitive's in flat array. */
private collectCurvePrimitivesGo(results: CurvePrimitive[], smallestPossiblePrimitives: boolean) {
private collectCurvePrimitivesGo(results: CurvePrimitive[], smallestPossiblePrimitives: boolean, explodeLinestrings: boolean = false) {
if (this.children) {
for (const child of this.children) {
if (child instanceof CurvePrimitive)
child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives);
child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLinestrings);
else if (child instanceof CurveCollection)
child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives);
child.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLinestrings);
}
}
}
Expand All @@ -96,9 +96,9 @@ export abstract class CurveCollection extends GeometryQuery {
* @param collectorArray optional array to receive primitives. If present, new primitives are ADDED (without clearing the array.)
* @param smallestPossiblePrimitives if false, CurvePrimitiveWithDistanceIndex returns only itself. If true, it recurses to its (otherwise hidden) children.
*/
public collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives: boolean = false): CurvePrimitive[] {
public collectCurvePrimitives(collectorArray?: CurvePrimitive[], smallestPossiblePrimitives: boolean = false, explodeLineStrings: boolean = false): CurvePrimitive[] {
const results: CurvePrimitive[] = collectorArray === undefined ? [] : collectorArray;
this.collectCurvePrimitivesGo(results, smallestPossiblePrimitives);
this.collectCurvePrimitivesGo(results, smallestPossiblePrimitives, explodeLineStrings);
return results;
}

Expand Down Expand Up @@ -189,21 +189,22 @@ export abstract class CurveChain extends CurveCollection {
return this._curves;
}
/**
* Return curve primitive by index, interpreted cyclically for both Loop and Path
* @param index index to array
*/
/**
* Return the `[index]` curve primitive, using `modulo` to map`index` to the cyclic indexing.
* Return the `[index]` curve primitive, optionally using `modulo` to map`index` to the cyclic indexing.
* * In particular, `-1` is the final curve.
* @param index cyclic index
*/
public cyclicCurvePrimitive(index: number): CurvePrimitive | undefined {
public cyclicCurvePrimitive(index: number, cyclic: boolean = true): CurvePrimitive | undefined {
const n = this.children.length;
if (n === 0)
return undefined;

const index2 = Geometry.modulo(index, n);
return this.children[index2];
/** Try simplest non-cyclic access first . . */
if (index >= 0 && index < n)
return this.children[index];
if (cyclic) {
const index2 = Geometry.modulo(index, n);
return this.children[index2];
}
return undefined;
}
/** Stroke the chain into a simple xyz array.
* @param options tolerance parameters controlling the stroking.
Expand Down Expand Up @@ -261,6 +262,16 @@ export abstract class CurveChain extends CurveCollection {
curve.reverseInPlace();
this._curves.reverse();
}
/** Evaluate an indexed curve at a fraction. Return as a CurveLocationDetail that indicates the primitive.
*/

public primitiveIndexAndFractionToCurveLocationDetailPointAndDerivative(index: number, fraction: number, cyclic: boolean = false, result?: CurveLocationDetail): CurveLocationDetail | undefined {
const primitive = this.cyclicCurvePrimitive(index, cyclic);
if (primitive) {
return CurveLocationDetail.createCurveEvaluatedFractionPointAndDerivative(primitive, fraction, result);
}
return undefined;
}
}

/**
Expand Down
33 changes: 25 additions & 8 deletions core/geometry/src/curve/CurveLocationDetail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module Curve
*/
import { Geometry } from "../Geometry";
import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d";
import { Ray3d } from "../geometry3d/Ray3d";
import { CurvePrimitive } from "./CurvePrimitive";

/** @packageDocumentation
* @module Curve
*/
import { Geometry } from "../Geometry";
import { Point3d, Vector3d } from "../geometry3d/Point3dVector3d";
import { Ray3d } from "../geometry3d/Ray3d";
import { CurvePrimitive } from "./CurvePrimitive";

/**
* An enumeration of special conditions being described by a CurveLocationDetail.
* @public
Expand Down Expand Up @@ -303,6 +303,23 @@ export class CurveLocationDetail {
result.a = 0.0;
return result;
}
/** create with CurvePrimitive pointer and fraction for evaluation.
*/
public static createCurveEvaluatedFractionPointAndDerivative(
curve: CurvePrimitive,
fraction: number,
result?: CurveLocationDetail): CurveLocationDetail {
result = result ? result : new CurveLocationDetail();
result.curve = curve;
result.fraction = fraction;
const ray = curve.fractionToPointAndDerivative(fraction);
result.point = ray.origin;
result.vectorInCurveLocationDetail = ray.direction;
result.curveSearchStatus = undefined;
result.a = 0.0;
return result;
}

/** create with CurvePrimitive pointer and 2 fractions for evaluation.
*/
public static createCurveEvaluatedFractionFraction(
Expand Down
Loading

0 comments on commit b99a594

Please sign in to comment.