Skip to content
9 changes: 9 additions & 0 deletions common/api/core-geometry.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -692,6 +692,7 @@ export class Box extends SolidPrimitive {
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
readonly solidPrimitiveType = "box";
strokeConstantVSection(zFraction: number): LineString3d;
tryTransformInPlace(transform: Transform): boolean;
Expand Down Expand Up @@ -1378,6 +1379,7 @@ export class Cone extends SolidPrimitive implements UVSurface, UVSurfaceIsoParam
static createAxisPoints(centerA: Point3d, centerB: Point3d, radiusA: number, radiusB: number, capped?: boolean): Cone | undefined;
static createBaseAndTarget(centerA: Point3d, centerB: Point3d, vectorX: Vector3d, vectorY: Vector3d, radiusA: number, radiusB: number, capped?: boolean): Cone;
static createDgnCone(centerA: Point3d, centerB: Point3d, vectorX: Vector3d, vectorY: Vector3d, radiusA: number, radiusB: number, capped?: boolean): Cone | undefined;
cylinderRadius(allowSkew?: boolean): number;
dispatchToGeometryHandler(handler: GeometryHandler): any;
extendRange(rangeToExtend: Range3d, transform?: Transform): void;
getCenterA(): Point3d;
Expand All @@ -1391,6 +1393,7 @@ export class Cone extends SolidPrimitive implements UVSurface, UVSurfaceIsoParam
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
maxIsoParametricDistance(): Vector2d;
readonly solidPrimitiveType = "cone";
strokeConstantVSection(v: number, fixedStrokeCount?: number, options?: StrokeOptions): LineString3d;
Expand Down Expand Up @@ -3380,6 +3383,7 @@ export class LinearSweep extends SolidPrimitive {
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
readonly solidPrimitiveType = "linearSweep";
tryTransformInPlace(transform: Transform): boolean;
}
Expand Down Expand Up @@ -3662,6 +3666,7 @@ export class Matrix3d implements BeJSONFunctions {
coffs: Float64Array;
columnDotXYZ(columnIndex: AxisIndex, x: number, y: number, z: number): number;
columnX(result?: Vector3d): Vector3d;
columnXCrossColumnY(result?: Vector3d): Vector3d;
columnXDotColumnY(): number;
columnXDotColumnZ(): number;
columnXMagnitude(): number;
Expand Down Expand Up @@ -5626,6 +5631,7 @@ export class RotationalSweep extends SolidPrimitive {
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
readonly solidPrimitiveType = "rotationalSweep";
tryTransformInPlace(transform: Transform): boolean;
}
Expand Down Expand Up @@ -5790,6 +5796,7 @@ export abstract class SolidPrimitive extends GeometryQuery {
readonly geometryCategory = "solid";
abstract getConstructiveFrame(): Transform | undefined;
abstract get isClosedVolume(): boolean;
get isSkew(): boolean;
abstract readonly solidPrimitiveType: SolidPrimitiveType;
}

Expand Down Expand Up @@ -5849,6 +5856,7 @@ export class Sphere extends SolidPrimitive implements UVSurface {
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
get latitudeSweepFraction(): number;
maxAxisRadius(): number;
maxIsoParametricDistance(): Vector2d;
Expand Down Expand Up @@ -6048,6 +6056,7 @@ export class TorusPipe extends SolidPrimitive implements UVSurface, UVSurfaceIso
isAlmostEqual(other: GeometryQuery): boolean;
get isClosedVolume(): boolean;
isSameGeometryClass(other: any): boolean;
get isSkew(): boolean;
maxIsoParametricDistance(): Vector2d;
readonly solidPrimitiveType = "torusPipe";
tryTransformInPlace(transform: Transform): boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@itwin/core-geometry",
"comment": "Add SolidPrimitive.isSkew and Cone.cylinderRadius",
"type": "none"
}
],
"packageName": "@itwin/core-geometry"
}
4 changes: 4 additions & 0 deletions core/geometry/src/geometry3d/Matrix3d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1455,6 +1455,10 @@ export class Matrix3d implements BeJSONFunctions {
public rowZMagnitude(): number {
return Geometry.hypotenuseXYZ(this.coffs[6], this.coffs[7], this.coffs[8]);
}
/** Return the cross product of column X with column Y. */
public columnXCrossColumnY(result?: Vector3d): Vector3d {
return Geometry.crossProductXYZXYZ(this.coffs[0], this.coffs[3], this.coffs[6], this.coffs[1], this.coffs[4], this.coffs[7], result);
}
/** Return the dot product of column X with column Y */
public columnXDotColumnY(): number {
return this.coffs[0] * this.coffs[1]
Expand Down
21 changes: 8 additions & 13 deletions core/geometry/src/serialization/IModelJsonSchema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1467,20 +1467,15 @@ export namespace IModelJson {
const xySameLength = Geometry.isSameCoordinate(xMag, yMag);
const axisVector = Vector3d.createStartEnd(centerA, centerB);

// special case of cylinder
if (Geometry.isSameCoordinate(radiusA, radiusB)
&& vectorX.isPerpendicularTo(axisVector)
&& vectorY.isPerpendicularTo(axisVector)
&& xySameLength
&& Geometry.isSameCoordinate(xMag, 1.0)) {
Comment thread
saeeedtorabi marked this conversation as resolved.
return {
cylinder: {
capped: data.capped,
start: centerA.toJSON(),
end: centerB.toJSON(),
radius: radiusA,
},
const cylinderRadius = data.cylinderRadius();
if (cylinderRadius > 0) {
const cylinderProps: CylinderProps = {
capped: data.capped,
start: centerA.toJSON(),
end: centerB.toJSON(),
radius: cylinderRadius,
};
return { cylinder: cylinderProps };
}

const coneProps: ConeProps = {
Expand Down
4 changes: 4 additions & 0 deletions core/geometry/src/solid/Box.ts
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,10 @@ export class Box extends SolidPrimitive {
rangeToExtend.extendTransformedXYZ(boxTransform, bx, by, 1);
}
}
/** Return true if the solid's local z-axis is not perpendicular to its local xy-plane. */
public override get isSkew(): boolean {
return !this._localToWorld.matrix.columnZ().isParallelTo(this._localToWorld.matrix.columnXCrossColumnY(), true, true);
Comment thread
saeeedtorabi marked this conversation as resolved.
}
/**
* @return true if this is a closed volume.
*/
Expand Down
23 changes: 23 additions & 0 deletions core/geometry/src/solid/Cone.ts
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,29 @@ export class Cone extends SolidPrimitive implements UVSurface, UVSurfaceIsoParam
this._localToWorld.multiplyVectorXYZ(drdv * cosTheta, drdv * sinTheta, 1.0),
result);
}
/** Return true if the solid's local z-axis is not perpendicular to its local xy-plane. */
public override get isSkew(): boolean {
return !this._localToWorld.matrix.columnZ().isParallelTo(this._localToWorld.matrix.columnXCrossColumnY(), true, true);
}
/**
* Test if this cone is a cylinder.
* * A cone is cylindrical if both conditions hold:
* 1. cross sections are circles of equal radius
* 2. axis is perpendicular to cross sections
* * Radii within [[Geometry.smallMetricDistance]] are considered equal.
* @param allowSkew whether to test the first condition only. Default value is `false`: test both conditions.
* @return cross sectional radius > 0 if cylindrical cone; otherwise 0 for non-cylindrical or degenerate cone.
*/
public cylinderRadius(allowSkew: boolean = false): number {
let radius = 0;
if (allowSkew || !this.isSkew) {
const magX = this._localToWorld.matrix.columnXMagnitude();
const magY = this._localToWorld.matrix.columnYMagnitude();
if (Geometry.isSameCoordinate(magX, magY) && Geometry.isSameCoordinate(this._radiusA, this._radiusB))
radius = Math.abs(magX * this._radiusA);
}
return radius;
}
/**
* @return true if this is a closed volume.
*/
Expand Down
4 changes: 4 additions & 0 deletions core/geometry/src/solid/LinearSweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,10 @@ export class LinearSweep extends SolidPrimitive {
}
rangeToExtend.extendRange(contourRange);
}
/** Return true if the solid's local z-axis is not perpendicular to its local xy-plane. */
public override get isSkew(): boolean {
return !this._direction.isParallelTo(this._contour.localToWorld.matrix.columnXCrossColumnY(), true, true);
}
/**
* @return true if this is a closed volume.
*/
Expand Down
4 changes: 4 additions & 0 deletions core/geometry/src/solid/RotationalSweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,10 @@ export class RotationalSweep extends SolidPrimitive {
strokes.extendRange(range, this.getFractionalRotationTransform(i / numStep, stepTransform));
}
}
/** Return true if the sweep axis and contour are not coplanar. */
public override get isSkew(): boolean {
return !this._normalizedAxis.direction.isPerpendicularTo(this._contour.localToWorld.matrix.columnXCrossColumnY(), true);
}
/** Specify if the sweep forms a closed volume. */
public get isClosedVolume(): boolean {
return this.capped || this._sweepAngle.isFullCircle;
Expand Down
2 changes: 1 addition & 1 deletion core/geometry/src/solid/RuledSweep.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import { SweepContour } from "./SweepContour";

/**
* Type for a function argument taking 2 curves and returning another curve or failing with undefined.
* * This is used (for instance) by `RuleSweep.mutatePartners`.
* * This is used (for instance) by [[RuledSweep.mutatePartners]].
* @public
*/
export type CurvePrimitiveMutator = (primitiveA: CurvePrimitive, primitiveB: CurvePrimitive) => CurvePrimitive | undefined;
Expand Down
11 changes: 10 additions & 1 deletion core/geometry/src/solid/SolidPrimitive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,19 @@ export abstract class SolidPrimitive extends GeometryQuery {
/** Return a cross section at specified vFraction. */
public abstract constantVSection(_vFraction: number): CurveCollection | undefined;
/**
* Return a Transform from the local system of the solid to world.
* Return a Transform from the solid's local coordinate system to world.
* * The particulars of origin and orientation are specific to each SolidPrimitive type.
* * The returned Transform is generally rigid (no preservation of skew, mirror, or scale in the solid's definition).
*/
public abstract getConstructiveFrame(): Transform | undefined;
/**
* Return true if the solid's local coordinate axes lack full orthogonality.
* * Skew typically takes the form of a local z-axis that is not perpendicular to the local xy-plane.
* * This property is always `false` for a [[RuledSweep]].
*/
public get isSkew(): boolean {
return false;
}
/**
* Return true if this is a closed volume
* * LinearSweep, Box, Cone only depend on capped.
Expand Down
4 changes: 4 additions & 0 deletions core/geometry/src/solid/Sphere.ts
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,10 @@ export class Sphere extends SolidPrimitive implements UVSurface {
this._localToWorld.matrix.multiplyXYZ(-fPhi * cosTheta * sinPhi, -fPhi * sinTheta * sinPhi, fPhi * cosPhi),
result);
}
/** Return true if the solid's local z-axis is not perpendicular to its local xy-plane. */
public override get isSkew(): boolean {
return !this._localToWorld.matrix.columnZ().isParallelTo(this._localToWorld.matrix.columnXCrossColumnY(), true, true);
}
/**
* * A sphere is can be closed two ways:
* * full sphere (no caps needed for closure)
Expand Down
4 changes: 4 additions & 0 deletions core/geometry/src/solid/TorusPipe.ts
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,10 @@ export class TorusPipe extends SolidPrimitive implements UVSurface, UVSurfaceIso
const b = Math.abs(this.getMinorRadius());
return Vector2d.create(b * Math.PI * 2.0, (a + b) * this._sweep.radians);
}
/** Return true if the solid's local z-axis is not perpendicular to its local xy-plane. */
public override get isSkew(): boolean {
return !this._localToWorld.matrix.columnZ().isParallelTo(this._localToWorld.matrix.columnXCrossColumnY(), true, true);
}
/**
* @return true if this is a closed volume.
*/
Expand Down
20 changes: 20 additions & 0 deletions core/geometry/src/test/curve/Curve.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { BSplineCurve3d, BSplineCurve3dBase } from "../../bspline/BSplineCurve";
import { BSplineCurve3dH } from "../../bspline/BSplineCurve3dH";
import { InterpolationCurve3d } from "../../bspline/InterpolationCurve3d";
import { Arc3d } from "../../curve/Arc3d";
import { ConstructCurveBetweenCurves } from "../../curve/ConstructCurveBetweenCurves";
import { CoordinateXYZ } from "../../curve/CoordinateXYZ";
import { CurveChainWithDistanceIndex } from "../../curve/CurveChainWithDistanceIndex";
import { BagOfCurves, CurveCollection } from "../../curve/CurveCollection";
Expand Down Expand Up @@ -1472,3 +1473,22 @@ describe("GeometryQuery", () => {
expect(ck.getNumErrors()).toBe(0);
});
});

describe("CurveBetweenCurves", () => {
it("Mismatches", () => {
const ck = new Checker();
const segment = LineSegment3d.createXYZXYZ(1, 2, 2, 4, 2, -1);
const arc = Arc3d.createUnitCircle();
const points = [Point3d.create(0, 0, 0), Point3d.create(1, 1, 0), Point3d.create(3, 1, 0), Point3d.create(3, 0, 0)];
const bcurve = BSplineCurve3d.createUniformKnots(points, 3)!;
const linestring = LineString3d.create(points);
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(segment, 0.5, arc));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(segment, 0.5, linestring));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(segment, 0.5, bcurve));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(arc, 0.5, linestring));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(linestring, 0.5, arc));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(arc, 0.5, bcurve));
ck.testUndefined(ConstructCurveBetweenCurves.interpolateBetween(bcurve, 0.5, segment));
expect(ck.getNumErrors()).toBe(0);
});
});
Loading
Loading