@itwin/core-backend
Version:
iTwin.js backend components
249 lines • 14 kB
JavaScript
"use strict";
/*---------------------------------------------------------------------------------------------
* Copyright (c) Bentley Systems, Incorporated. All rights reserved.
* See LICENSE.md in the project root for license terms and full copyright notice.
*--------------------------------------------------------------------------------------------*/
/** @packageDocumentation
* @module ElementGeometry
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.appendFrameToBuilder = appendFrameToBuilder;
exports.computeFrame = computeFrame;
exports.computeIntervalPoints = computeIntervalPoints;
const core_common_1 = require("@itwin/core-common");
const core_geometry_1 = require("@itwin/core-geometry");
/**
* Based on the frame style, this method will construct and append [[GeometryParams]] (for line style) and a [[Loop]] (for the frame shape) to the builder.
* @param builder that will be appended to in place
* @param frame
* @param range to enclose with the frame
* @param transform that transforms the range to world coordinates
* @returns `true` if any geometry was appended to the builder
* @beta
*/
function appendFrameToBuilder(builder, frame, range, transform, geomParams) {
if (frame.shape === "none" || frame.shape === undefined) {
return false;
}
const params = geomParams.clone();
if (frame.fill === "none" || frame.fill === undefined) {
params.fillDisplay = core_common_1.FillDisplay.Never;
}
else if (frame.fill === "background") {
params.backgroundFill = core_common_1.BackgroundFill.Solid;
params.fillDisplay = core_common_1.FillDisplay.Blanking;
}
else if (frame.fill !== "subcategory") {
params.fillColor = core_common_1.ColorDef.fromJSON(frame.fill);
params.lineColor = params.fillColor;
params.fillDisplay = core_common_1.FillDisplay.Blanking;
}
if (frame.border !== "subcategory") {
params.lineColor = core_common_1.ColorDef.fromJSON(frame.border);
params.weight = frame.borderWeight;
}
const frameGeometry = computeFrame({ frame: frame.shape, range, transform });
if (!builder.appendGeometryParamsChange(params) || !builder.appendGeometryQuery(frameGeometry)) {
return false;
}
// The tile generator does not produce an outline for shapes with blanking fill. We must add the outline separately.
if (params.fillDisplay === core_common_1.FillDisplay.Blanking) {
const outlineParams = params.clone();
outlineParams.fillDisplay = core_common_1.FillDisplay.Never;
if (!builder.appendGeometryParamsChange(outlineParams) || !builder.appendGeometryQuery(frameGeometry)) {
return false;
}
}
return true;
}
/**
* Computes the frame geometry based on the provided frame shape and range.
* @returns a [Loop]($geometry) or [Path]($geometry) (if it's just a line) that represents the frame geometry
* @beta
*/
function computeFrame(args) {
switch (args.frame) {
case "line": return computeLine(args.range, args.transform);
case "rectangle": return computeRectangle(args.range, args.transform);
case "circle": return computeCircle(args.range, args.transform);
case "equilateralTriangle": return computeTriangle(args.range, args.transform);
case "diamond": return computeDiamond(args.range, args.transform);
case "square": return computeSquare(args.range, args.transform);
case "pentagon": return computePolygon(5, args.range, args.transform, 90);
case "hexagon": return computePolygon(6, args.range, args.transform);
case "octagon": return computePolygon(8, args.range, args.transform, 180 / 8); // or pi/8 in radians
case "capsule": return computeCapsule(args.range, args.transform);
case "roundedRectangle": return computeRoundedRectangle(args.range, args.transform);
default: return computeRectangle(args.range, args.transform);
}
}
/**
* Computes points along the edges of the frame geometry based on the provided frame shape, range, and interval factors.
* These can be used for snapping or attaching leaders.
* @returns an array of [[Point3d]] that represent the points along the edges of the frame geometry. Returns `undefined` if the loop created by `computeFrame` is empty.
* @beta
*/
function computeIntervalPoints({ frame, range, transform, lineIntervalFactor = 0.5, arcIntervalFactor = 0.25 }) {
const points = [];
const curves = computeFrame({ frame, range, transform }).collectCurvePrimitives(undefined, false, true);
curves.forEach((curve) => {
const end = curve instanceof core_geometry_1.Arc3d ? arcIntervalFactor : lineIntervalFactor;
for (let interval = 0; interval <= 1; interval += end) {
points.push(curve.fractionToPoint(interval));
}
});
return points;
}
/** Line - currently just adds an underline. Once we have leaders, this method may change. */
const computeLine = (range, transform) => {
const points = [core_geometry_1.Point3d.create(range.low.x, range.low.y), core_geometry_1.Point3d.create(range.high.x, range.low.y)];
const frame = core_geometry_1.LineString3d.createPoints(points);
return core_geometry_1.Path.create(frame.cloneTransformed(transform));
};
/** Rectangle - simplest frame */
const computeRectangle = (range, transform) => {
const points = range.corners3d(true);
const frame = core_geometry_1.LineString3d.createPoints(points);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
/** Rounded Rectangle: each corner will be turned into an arc with the radius of the arc being the @param radiusFactor * the height (yLength) of the range */
const computeRoundedRectangle = (range, transform, radiusFactor = 0.25) => {
const radius = range.yLength() * radiusFactor * Math.sqrt(2);
// We're going to circumscribe the range with our rounded edges. The corners of the range will fall on 45 degree angles.
const radiusOffsetFactor = range.yLength() * radiusFactor;
// These values are the origins of the circles
const inLeft = range.low.x + radiusOffsetFactor;
const inRight = range.high.x - radiusOffsetFactor;
const inBottom = range.low.y + radiusOffsetFactor;
const inTop = range.high.y - radiusOffsetFactor;
// These values exist on the circles
const exLeft = inLeft - radius;
const exRight = inRight + radius;
const exBottom = inBottom - radius;
const exTop = inTop + radius;
const q1 = core_geometry_1.AngleSweep.createStartEndDegrees(0, 90);
const q2 = core_geometry_1.AngleSweep.createStartEndDegrees(90, 180);
const q3 = core_geometry_1.AngleSweep.createStartEndDegrees(180, 270);
const q4 = core_geometry_1.AngleSweep.createStartEndDegrees(270, 360);
const curves = [
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(inRight, exTop), core_geometry_1.Point3d.create(inLeft, exTop)]), // top
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inLeft, inTop), radius, q2), // top left
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(exLeft, inTop), core_geometry_1.Point3d.create(exLeft, inBottom)]), // left
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inLeft, inBottom), radius, q3), // bottom left
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(inLeft, exBottom), core_geometry_1.Point3d.create(inRight, exBottom)]), // bottom
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inRight, inBottom), radius, q4), // bottom right
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(exRight, inBottom), core_geometry_1.Point3d.create(exRight, inTop)]), // right
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inRight, inTop), radius, q1), // top right
];
return core_geometry_1.Loop.createArray(curves.map((curve) => curve.cloneTransformed(transform)));
};
/** Circle */
const computeCircle = (range, transform) => {
const radius = range.low.distance(range.high) / 2;
const frame = core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.createFrom(range.center), radius);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
/** Equilateral Triangle */
const computeTriangle = (range, transform) => {
const xLength = range.xLength();
const yLength = range.yLength();
const center = range.center;
const points = [];
const magnitude = (xLength > yLength) ? (xLength * Math.sqrt(3) + yLength) / 2 : (yLength * Math.sqrt(3) + xLength) / 2;
const v1 = core_geometry_1.Vector2d.create(0, magnitude);
const vectors = [
v1, // top
v1.rotateXY(core_geometry_1.Angle.createDegrees(120)), // left
v1.rotateXY(core_geometry_1.Angle.createDegrees(240)), // right
v1 // top
];
vectors.forEach((v) => {
points.push(core_geometry_1.Point3d.create(center.x + v.x, center.y + v.y));
});
const frame = core_geometry_1.LineString3d.createPoints(points);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
/** Diamond (square rotated 45 degrees) */
const computeDiamond = (range, transform) => {
const offset = (range.xLength() + range.yLength()) / 2;
const center = range.center;
const points = [
core_geometry_1.Point3d.createFrom({ x: center.x, y: center.y + offset }), // top
core_geometry_1.Point3d.createFrom({ x: center.x + offset, y: center.y }), // right
core_geometry_1.Point3d.createFrom({ x: center.x, y: center.y - offset }), // bottom
core_geometry_1.Point3d.createFrom({ x: center.x - offset, y: center.y }), // left
core_geometry_1.Point3d.createFrom({ x: center.x, y: center.y + offset }), // top
];
const frame = core_geometry_1.LineString3d.createPoints(points);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
/** Square */
const computeSquare = (range, transform) => {
// Extend range
const xLength = range.xLength() / 2;
const yLength = range.yLength() / 2;
const center = range.center;
if (xLength > yLength) {
range.extendPoint({ x: center.x, y: center.y + xLength });
range.extendPoint({ x: center.x, y: center.y - xLength });
}
else {
range.extendPoint({ x: center.x + yLength, y: center.y });
range.extendPoint({ x: center.x - yLength, y: center.y });
}
const points = range.corners3d(true);
const frame = core_geometry_1.LineString3d.createPoints(points);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
/** Capsule (or pill shape) */
const computeCapsule = (range, transform) => {
const height = range.yLength();
const radius = height * (Math.sqrt(2) / 2);
// We're going to circumscribe the range with our rounded edges. The corners of the range will fall on 45 degree angles.
const radiusOffsetFactor = height / 2;
// These values are the origins of the circles
const inLeft = range.low.x + radiusOffsetFactor;
const inRight = range.high.x - radiusOffsetFactor;
const inBottom = range.low.y + radiusOffsetFactor;
const inTop = range.high.y - radiusOffsetFactor;
// These values exist on the circles
const exBottom = inBottom - radius;
const exTop = inTop + radius;
const leftHalfCircle = core_geometry_1.AngleSweep.createStartEndDegrees(90, 270);
const rightHalfCircle = core_geometry_1.AngleSweep.createStartEndDegrees(-90, 90);
const curves = [
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(inRight, exTop), core_geometry_1.Point3d.create(inLeft, exTop)]), // top
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inLeft, range.center.y), radius, leftHalfCircle), // left
core_geometry_1.LineString3d.create([core_geometry_1.Point3d.create(inLeft, exBottom), core_geometry_1.Point3d.create(inRight, exBottom)]), // bottom
core_geometry_1.Arc3d.createXY(core_geometry_1.Point3d.create(inRight, range.center.y), radius, rightHalfCircle), // right
];
return core_geometry_1.Loop.createArray(curves.map((curve) => curve.cloneTransformed(transform)));
};
/** Regular polygon with n sides: note, this a generic method that can be used to create any polygon, but the frame will not be as tightly encapsulating. */
const computePolygon = (n, range, transform, angleOffset = 0) => {
// These are math terms: cspell:ignore inradius circumradius
if (n < 3)
throw new Error("A polygon must have at least 3 sides.");
// We're assuming the polygon is a regular polygon with `n` sides.
// The center of the polygon is the center of the range.
const center = range.center;
// The inradius is the distance from the center to the midpoint of each side of the polygon. On our range, this coincides with the distance from the center to one of its corners.
const inradius = range.low.distance(range.high) / 2;
// The circumradius is the distance from the center to each vertex of the polygon.
const circumradius = inradius / Math.cos(Math.PI / n);
// The exterior angles add up to 360 degrees.
const angleIncrement = 360 / n;
const vertices = [];
// Add a point for each vertex
for (let i = 0; i < n; i++) {
const angle = core_geometry_1.Angle.createDegrees(i * angleIncrement + angleOffset);
const vector = core_geometry_1.Vector2d.createPolar(circumradius, angle);
vertices.push(core_geometry_1.Point3d.create(center.x + vector.x, center.y + vector.y));
}
// Close the polygon
vertices.push(vertices[0]);
// Finally compute the loop!
const frame = core_geometry_1.LineString3d.createPoints(vertices);
return core_geometry_1.Loop.create(frame.cloneTransformed(transform));
};
//# sourceMappingURL=FrameGeometry.js.map