replicad
Version:
The library to build browser based 3D models with code
1,635 lines • 613 kB
JavaScript
(function(global, factory) {
typeof exports === "object" && typeof module !== "undefined" ? factory(exports) : typeof define === "function" && define.amd ? define(["exports"], factory) : (global = typeof globalThis !== "undefined" ? globalThis : global || self, factory(global.replicad = {}));
})(this, function(exports2) {
"use strict";
const OC = {
library: null
};
const setOC = (oc) => {
OC.library = oc;
};
const getOC = () => {
if (!OC.library)
throw new Error("oppencascade has not been loaded");
return OC.library;
};
if (!globalThis.FinalizationRegistry) {
console.log("Garbage collection will not work");
globalThis.FinalizationRegistry = () => ({
register: () => null,
unregister: () => null
});
}
const deletetableRegistry = new globalThis.FinalizationRegistry(
(heldValue) => {
try {
heldValue.delete();
} catch (e) {
console.error(e);
}
}
);
class WrappingObj {
constructor(wrapped) {
this.oc = getOC();
if (wrapped) {
deletetableRegistry.register(this, wrapped, wrapped);
}
this._wrapped = wrapped;
}
get wrapped() {
if (this._wrapped === null)
throw new Error("This object has been deleted");
return this._wrapped;
}
set wrapped(newWrapped) {
if (this._wrapped) {
deletetableRegistry.unregister(this._wrapped);
this._wrapped.delete();
}
deletetableRegistry.register(this, newWrapped, newWrapped);
this._wrapped = newWrapped;
}
delete() {
var _a;
deletetableRegistry.unregister(this.wrapped);
(_a = this.wrapped) == null ? void 0 : _a.delete();
this._wrapped = null;
}
}
const GCWithScope = () => {
function gcWithScope(value) {
deletetableRegistry.register(gcWithScope, value);
return value;
}
return gcWithScope;
};
const GCWithObject = (obj) => {
function registerForGC(value) {
deletetableRegistry.register(obj, value);
return value;
}
return registerForGC;
};
const localGC = (debug) => {
const cleaner = /* @__PURE__ */ new Set();
return [
(v) => {
cleaner.add(v);
return v;
},
() => {
[...cleaner.values()].forEach((d) => d.delete());
cleaner.clear();
},
debug ? cleaner : void 0
];
};
const HASH_CODE_MAX = 2147483647;
const DEG2RAD = Math.PI / 180;
const RAD2DEG = 180 / Math.PI;
const round3 = (v) => Math.round(v * 1e3) / 1e3;
function isPoint(p) {
if (Array.isArray(p))
return p.length === 3 || p.length === 2;
else if (p instanceof Vector)
return true;
else if (p && typeof (p == null ? void 0 : p.XYZ) === "function")
return true;
return false;
}
const makeAx3 = (center, dir, xDir) => {
const oc = getOC();
const origin = asPnt(center);
const direction = asDir(dir);
let axis;
if (xDir) {
const xDirection = asDir(xDir);
axis = new oc.gp_Ax3_3(origin, direction, xDirection);
xDirection.delete();
} else {
axis = new oc.gp_Ax3_4(origin, direction);
}
origin.delete();
direction.delete();
return axis;
};
const makeAx2 = (center, dir, xDir) => {
const oc = getOC();
const origin = asPnt(center);
const direction = asDir(dir);
let axis;
if (xDir) {
const xDirection = asDir(xDir);
axis = new oc.gp_Ax2_2(origin, direction, xDirection);
xDirection.delete();
} else {
axis = new oc.gp_Ax2_3(origin, direction);
}
origin.delete();
direction.delete();
return axis;
};
const makeAx1 = (center, dir) => {
const oc = getOC();
const origin = asPnt(center);
const direction = asDir(dir);
const axis = new oc.gp_Ax1_2(origin, direction);
origin.delete();
direction.delete();
return axis;
};
const makeVec = (vector = [0, 0, 0]) => {
const oc = getOC();
if (Array.isArray(vector)) {
if (vector.length === 3)
return new oc.gp_Vec_4(...vector);
else if (vector.length === 2)
return new oc.gp_Vec_4(...vector, 0);
} else if (vector instanceof Vector) {
return new oc.gp_Vec_3(vector.wrapped.XYZ());
} else if (vector.XYZ)
return new oc.gp_Vec_3(vector.XYZ());
return new oc.gp_Vec_4(0, 0, 0);
};
class Vector extends WrappingObj {
constructor(vector = [0, 0, 0]) {
super(makeVec(vector));
}
get repr() {
return `x: ${round3(this.x)}, y: ${round3(this.y)}, z: ${round3(this.z)}`;
}
get x() {
return this.wrapped.X();
}
get y() {
return this.wrapped.Y();
}
get z() {
return this.wrapped.Z();
}
get Length() {
return this.wrapped.Magnitude();
}
toTuple() {
return [this.x, this.y, this.z];
}
cross(v) {
return new Vector(this.wrapped.Crossed(v.wrapped));
}
dot(v) {
return this.wrapped.Dot(v.wrapped);
}
sub(v) {
return new Vector(this.wrapped.Subtracted(v.wrapped));
}
add(v) {
return new Vector(this.wrapped.Added(v.wrapped));
}
multiply(scale2) {
return new Vector(this.wrapped.Multiplied(scale2));
}
normalized() {
return new Vector(this.wrapped.Normalized());
}
normalize() {
this.wrapped.Normalize();
return this;
}
getCenter() {
return this;
}
getAngle(v) {
return this.wrapped.Angle(v.wrapped) * RAD2DEG;
}
projectToPlane(plane) {
const base = plane.origin;
const normal = plane.zDir;
const v1 = this.sub(base);
const v2 = normal.multiply(v1.dot(normal) / normal.Length ** 2);
const projection = this.sub(v2);
v1.delete();
v2.delete();
return projection;
}
equals(other) {
return this.wrapped.IsEqual(other.wrapped, 1e-5, 1e-5);
}
toPnt() {
return new this.oc.gp_Pnt_2(this.wrapped.XYZ());
}
toDir() {
return new this.oc.gp_Dir_3(this.wrapped.XYZ());
}
rotate(angle, center = [0, 0, 0], direction = [0, 0, 1]) {
const ax = makeAx1(center, direction);
this.wrapped.Rotate(ax, angle * DEG2RAD);
ax.delete();
return this;
}
}
const DIRECTIONS$1 = {
X: [1, 0, 0],
Y: [0, 1, 0],
Z: [0, 0, 1]
};
function makeDirection(p) {
if (p === "X" || p === "Y" || p === "Z") {
return DIRECTIONS$1[p];
}
return p;
}
function asPnt(coords) {
const v = new Vector(coords);
const pnt2 = v.toPnt();
v.delete();
return pnt2;
}
function asDir(coords) {
const v = new Vector(coords);
const dir = v.toDir();
v.delete();
return dir;
}
class Transformation extends WrappingObj {
constructor(transform) {
const oc = getOC();
super(transform || new oc.gp_Trsf_1());
}
translate(xDistOrVector, yDist = 0, zDist = 0) {
const translation = new Vector(
typeof xDistOrVector === "number" ? [xDistOrVector, yDist, zDist] : xDistOrVector
);
this.wrapped.SetTranslation_1(translation.wrapped);
return this;
}
rotate(angle, position = [0, 0, 0], direction = [0, 0, 1]) {
const dir = asDir(direction);
const origin = asPnt(position);
const axis = new this.oc.gp_Ax1_2(origin, dir);
this.wrapped.SetRotation_1(axis, angle * DEG2RAD);
axis.delete();
dir.delete();
origin.delete();
return this;
}
mirror(inputPlane = "YZ", inputOrigin) {
const r = GCWithScope();
let origin;
let direction;
if (typeof inputPlane === "string") {
const plane = r(createNamedPlane(inputPlane, inputOrigin));
origin = plane.origin;
direction = plane.zDir;
} else if (inputPlane instanceof Plane) {
origin = inputOrigin || inputPlane.origin;
direction = inputPlane.zDir;
} else {
origin = inputOrigin || [0, 0, 0];
direction = inputPlane;
}
const mirrorAxis = r(makeAx2(origin, direction));
this.wrapped.SetMirror_3(mirrorAxis);
return this;
}
scale(center, scale2) {
const pnt2 = asPnt(center);
this.wrapped.SetScale(pnt2, scale2);
pnt2.delete();
return this;
}
coordSystemChange(fromSystem, toSystem) {
const r = GCWithScope();
const fromAx = r(
fromSystem === "reference" ? new this.oc.gp_Ax3_1() : makeAx3(fromSystem.origin, fromSystem.zDir, fromSystem.xDir)
);
const toAx = r(
toSystem === "reference" ? new this.oc.gp_Ax3_1() : makeAx3(toSystem.origin, toSystem.zDir, toSystem.xDir)
);
this.wrapped.SetTransformation_1(fromAx, toAx);
return this;
}
transformPoint(point) {
const pnt2 = asPnt(point);
const newPoint = pnt2.Transformed(this.wrapped);
pnt2.delete();
return newPoint;
}
transform(shape) {
const transformer = new this.oc.BRepBuilderAPI_Transform_2(
shape,
this.wrapped,
true
);
return transformer.ModifiedShape(shape);
}
}
class Plane {
constructor(origin, xDirection = null, normal = [0, 0, 1]) {
this.oc = getOC();
const zDir = new Vector(normal);
if (zDir.Length === 0) {
throw new Error("normal should be non null");
}
this.zDir = zDir.normalize();
let xDir;
if (!xDirection) {
const ax3 = makeAx3(origin, zDir);
xDir = new Vector(ax3.XDirection());
ax3.delete();
} else {
xDir = new Vector(xDirection);
}
if (xDir.Length === 0) {
throw new Error("xDir should be non null");
}
this.xDir = xDir.normalize();
this.yDir = this.zDir.cross(this.xDir).normalize();
this.origin = new Vector(origin);
}
delete() {
this.localToGlobal.delete();
this.xDir.delete();
this.yDir.delete();
this.zDir.delete();
this._origin.delete();
}
clone() {
return new Plane(this.origin, this.xDir, this.zDir);
}
get origin() {
return this._origin;
}
set origin(newOrigin) {
this._origin = newOrigin;
this._calcTransforms();
}
translateTo(point) {
const newPlane = this.clone();
newPlane.origin = new Vector(point);
return newPlane;
}
translate(xDistOrVector, yDist = 0, zDist = 0) {
const translation = new Vector(
typeof xDistOrVector === "number" ? [xDistOrVector, yDist, zDist] : xDistOrVector
);
return this.translateTo(this.origin.add(translation));
}
translateX(xDist) {
return this.translate(xDist, 0, 0);
}
translateY(yDist) {
return this.translate(0, yDist, 0);
}
translateZ(zDist) {
return this.translate(0, 0, zDist);
}
pivot(angle, direction = [1, 0, 0]) {
const dir = makeDirection(direction);
const zDir = new Vector(this.zDir).rotate(angle, [0, 0, 0], dir);
const xDir = new Vector(this.xDir).rotate(angle, [0, 0, 0], dir);
return new Plane(this.origin, xDir, zDir);
}
rotate2DAxes(angle) {
const xDir = new Vector(this.xDir).rotate(angle, [0, 0, 0], this.zDir);
return new Plane(this.origin, xDir, this.zDir);
}
_calcTransforms() {
const globalCoordSystem = new this.oc.gp_Ax3_1();
const localCoordSystem = makeAx3(this.origin, this.zDir, this.xDir);
const forwardT = new this.oc.gp_Trsf_1();
forwardT.SetTransformation_1(globalCoordSystem, localCoordSystem);
this.globalToLocal = new Transformation();
this.globalToLocal.coordSystemChange("reference", {
origin: this.origin,
zDir: this.zDir,
xDir: this.xDir
});
this.localToGlobal = new Transformation();
this.localToGlobal.coordSystemChange(
{
origin: this.origin,
zDir: this.zDir,
xDir: this.xDir
},
"reference"
);
}
setOrigin2d(x, y) {
this.origin = this.toWorldCoords([x, y]);
}
toLocalCoords(vec2) {
const pnt2 = this.globalToLocal.transformPoint(vec2);
const newVec = new Vector(pnt2);
pnt2.delete();
return newVec;
}
toWorldCoords(v) {
const pnt2 = this.localToGlobal.transformPoint(v);
const newVec = new Vector(pnt2);
pnt2.delete();
return newVec;
}
}
const PLANES_CONFIG = {
XY: {
xDir: [1, 0, 0],
normal: [0, 0, 1]
},
YZ: {
xDir: [0, 1, 0],
normal: [1, 0, 0]
},
ZX: {
xDir: [0, 0, 1],
normal: [0, 1, 0]
},
XZ: {
xDir: [1, 0, 0],
normal: [0, -1, 0]
},
YX: {
xDir: [0, 1, 0],
normal: [0, 0, -1]
},
ZY: {
xDir: [0, 0, 1],
normal: [-1, 0, 0]
},
front: {
xDir: [1, 0, 0],
normal: [0, 0, 1]
},
back: {
xDir: [-1, 0, 0],
normal: [0, 0, -1]
},
left: {
xDir: [0, 0, 1],
normal: [-1, 0, 0]
},
right: {
xDir: [0, 0, -1],
normal: [1, 0, 0]
},
top: {
xDir: [1, 0, 0],
normal: [0, 1, 0]
},
bottom: {
xDir: [1, 0, 0],
normal: [0, -1, 0]
}
};
const createNamedPlane = (plane, sourceOrigin = [0, 0, 0]) => {
const config = PLANES_CONFIG[plane];
if (!config)
throw new Error(`Could not find plane ${plane}`);
let origin;
if (typeof sourceOrigin === "number") {
origin = config.normal.map((v) => v * sourceOrigin);
} else {
origin = sourceOrigin;
}
return new Plane(origin, config.xDir, config.normal);
};
let BoundingBox$1 = class BoundingBox extends WrappingObj {
constructor(wrapped) {
const oc = getOC();
let boundBox = wrapped;
if (!boundBox) {
boundBox = new oc.Bnd_Box_1();
}
super(boundBox);
}
get repr() {
const [min, max] = this.bounds;
return `${new Vector(min).repr} - ${new Vector(max).repr}`;
}
get bounds() {
const xMin = { current: 0 };
const yMin = { current: 0 };
const zMin = { current: 0 };
const xMax = { current: 0 };
const yMax = { current: 0 };
const zMax = { current: 0 };
this.wrapped.Get(xMin, yMin, zMin, xMax, yMax, zMax);
return [
[xMin.current, yMin.current, zMin.current],
[xMax.current, yMax.current, zMax.current]
];
}
get center() {
const [[xmin, ymin, zmin], [xmax, ymax, zmax]] = this.bounds;
return [
xmin + (xmax - xmin) / 2,
ymin + (ymax - ymin) / 2,
zmin + (zmax - zmin) / 2
];
}
get width() {
const [[xmin], [xmax]] = this.bounds;
return Math.abs(xmax - xmin);
}
get height() {
const [[, ymin], [, ymax]] = this.bounds;
return Math.abs(ymax - ymin);
}
get depth() {
const [[, , zmin], [, , zmax]] = this.bounds;
return Math.abs(zmax - zmin);
}
add(other) {
this.wrapped.Add_1(other.wrapped);
}
isOut(other) {
return this.wrapped.IsOut_4(other.wrapped);
}
};
const makePlaneFromFace = (face, originOnSurface = [0, 0]) => {
const originPoint = face.pointOnSurface(...originOnSurface);
const normal = face.normalAt(originPoint);
const v = new Vector([0, 0, 1]);
let xd = v.cross(normal);
if (xd.Length < 1e-8) {
xd.delete();
xd = new Vector([1, 0, 0]);
}
v.delete();
return new Plane(originPoint, xd, normal);
};
function makePlane(plane = "XY", origin = [0, 0, 0]) {
if (plane instanceof Plane) {
return plane.clone();
} else {
return createNamedPlane(plane, origin);
}
}
function rotate(shape, angle, position = [0, 0, 0], direction = [0, 0, 1]) {
const transformation = new Transformation();
transformation.rotate(angle, position, direction);
const newShape = transformation.transform(shape);
transformation.delete();
return newShape;
}
function translate(shape, vector) {
const transformation = new Transformation();
transformation.translate(vector);
const newShape = transformation.transform(shape);
transformation.delete();
return newShape;
}
function mirror(shape, inputPlane, origin) {
const transformation = new Transformation();
transformation.mirror(inputPlane, origin);
const newShape = transformation.transform(shape);
transformation.delete();
return newShape;
}
function scale(shape, center, scale2) {
const transformation = new Transformation();
transformation.scale(center, scale2);
const newShape = transformation.transform(shape);
transformation.delete();
return newShape;
}
function isPoint2D(point) {
return Array.isArray(point) && point.length === 2;
}
let CURVE_TYPES_MAP = null;
const getCurveTypesMap = (refresh) => {
if (CURVE_TYPES_MAP && !refresh)
return CURVE_TYPES_MAP;
const oc = getOC();
const ga = oc.GeomAbs_CurveType;
CURVE_TYPES_MAP = /* @__PURE__ */ new Map([
[ga.GeomAbs_Line, "LINE"],
[ga.GeomAbs_Circle, "CIRCLE"],
[ga.GeomAbs_Ellipse, "ELLIPSE"],
[ga.GeomAbs_Hyperbola, "HYPERBOLA"],
[ga.GeomAbs_Parabola, "PARABOLA"],
[ga.GeomAbs_BezierCurve, "BEZIER_CURVE"],
[ga.GeomAbs_BSplineCurve, "BSPLINE_CURVE"],
[ga.GeomAbs_OffsetCurve, "OFFSET_CURVE"],
[ga.GeomAbs_OtherCurve, "OTHER_CURVE"]
]);
return CURVE_TYPES_MAP;
};
const findCurveType = (type) => {
let shapeType2 = getCurveTypesMap().get(type);
if (!shapeType2)
shapeType2 = getCurveTypesMap(true).get(type);
if (!shapeType2)
throw new Error("unknown type");
return shapeType2;
};
function precisionRound(number, precision) {
const factor = Math.pow(10, precision);
const n = precision < 0 ? number : 0.01 / factor + number;
return Math.round(n * factor) / factor;
}
function range(len) {
return Array.from(Array(len).keys());
}
function zip(arrays) {
const minLength = Math.min(...arrays.map((arr) => arr.length));
return range(minLength).map((i) => arrays.map((arr) => arr[i]));
}
function round2(v) {
return Math.round(v * 100) / 100;
}
const reprPnt = ([x, y]) => {
return `(${round2(x)},${round2(y)})`;
};
const asFixed = (p, precision = 1e-9) => {
let num = p;
if (Math.abs(p) < precision)
num = 0;
return num.toFixed(-Math.log10(precision));
};
const removeDuplicatePoints = (points, precision = 1e-9) => {
return Array.from(
new Map(
points.map(([p0, p1]) => [
`[${asFixed(p0, precision)},${asFixed(p1, precision)}]`,
[p0, p1]
])
).values()
);
};
const pnt = ([x, y]) => {
const oc = getOC();
return new oc.gp_Pnt2d_3(x, y);
};
const direction2d = ([x, y]) => {
const oc = getOC();
return new oc.gp_Dir2d_4(x, y);
};
const vec = ([x, y]) => {
const oc = getOC();
return new oc.gp_Vec2d_4(x, y);
};
const axis2d = (point, direction) => {
const oc = getOC();
const [r, gc] = localGC();
const axis = new oc.gp_Ax2d_2(r(pnt(point)), r(direction2d(direction)));
gc();
return axis;
};
class BoundingBox2d extends WrappingObj {
constructor(wrapped) {
const oc = getOC();
let boundBox = wrapped;
if (!boundBox) {
boundBox = new oc.Bnd_Box2d();
}
super(boundBox);
}
get repr() {
const [min, max] = this.bounds;
return `${reprPnt(min)} - ${reprPnt(max)}`;
}
get bounds() {
const xMin = { current: 0 };
const yMin = { current: 0 };
const xMax = { current: 0 };
const yMax = { current: 0 };
this.wrapped.Get(xMin, yMin, xMax, yMax);
return [
[xMin.current, yMin.current],
[xMax.current, yMax.current]
];
}
get center() {
const [[xmin, ymin], [xmax, ymax]] = this.bounds;
return [xmin + (xmax - xmin) / 2, ymin + (ymax - ymin) / 2];
}
get width() {
const [[xmin], [xmax]] = this.bounds;
return Math.abs(xmax - xmin);
}
get height() {
const [[, ymin], [, ymax]] = this.bounds;
return Math.abs(ymax - ymin);
}
outsidePoint(paddingPercent = 1) {
const [min, max] = this.bounds;
const width = max[0] - min[0];
const height = max[1] - min[1];
return [
max[0] + width / 100 * paddingPercent,
max[1] + height / 100 * paddingPercent * 0.9
];
}
add(other) {
this.wrapped.Add_1(other.wrapped);
}
isOut(other) {
return this.wrapped.IsOut_4(other.wrapped);
}
containsPoint(other) {
const r = GCWithScope();
const point = r(pnt(other));
return !this.wrapped.IsOut_1(point);
}
}
const samePoint$2 = ([x0, y0], [x1, y1], precision = 1e-6) => {
return Math.abs(x0 - x1) <= precision && Math.abs(y0 - y1) <= precision;
};
const add2d = ([x0, y0], [x1, y1]) => {
return [x0 + x1, y0 + y1];
};
const subtract2d = ([x0, y0], [x1, y1]) => {
return [x0 - x1, y0 - y1];
};
const scalarMultiply2d = ([x0, y0], scalar) => {
return [x0 * scalar, y0 * scalar];
};
const distance2d = ([x0, y0], [x1, y1] = [0, 0]) => {
return Math.sqrt((x0 - x1) ** 2 + (y0 - y1) ** 2);
};
const squareDistance2d = ([x0, y0], [x1, y1] = [0, 0]) => {
return (x0 - x1) ** 2 + (y0 - y1) ** 2;
};
function crossProduct2d([x0, y0], [x1, y1]) {
return x0 * y1 - y0 * x1;
}
const angle2d = ([x0, y0], [x1, y1] = [0, 0]) => {
return Math.atan2(y1 * x0 - y0 * x1, x0 * x1 + y0 * y1);
};
const polarAngle2d = ([x0, y0], [x1, y1] = [0, 0]) => {
return Math.atan2(y1 - y0, x1 - x0);
};
const normalize2d = ([x0, y0]) => {
const l = distance2d([x0, y0]);
return [x0 / l, y0 / l];
};
const rotate2d = (point, angle, center = [0, 0]) => {
const [px0, py0] = point;
const [cx, cy] = center;
const px = px0 - cx;
const py = py0 - cy;
const sinA = Math.sin(angle);
const cosA = Math.cos(angle);
const xnew = px * cosA - py * sinA;
const ynew = px * sinA + py * cosA;
return [xnew + cx, ynew + cy];
};
const polarToCartesian = (r, theta) => {
const x = Math.cos(theta) * r;
const y = Math.sin(theta) * r;
return [x, y];
};
const cartesianToPolar = ([x, y]) => {
const r = distance2d([x, y]);
const theta = Math.atan2(y, x);
return [r, theta];
};
const determinant2x2 = (matrix) => {
return matrix[0][0] * matrix[1][1] - matrix[0][1] * matrix[1][0];
};
class Curve2D extends WrappingObj {
constructor(handle) {
const oc = getOC();
const inner = handle.get();
super(new oc.Handle_Geom2d_Curve_2(inner));
this._boundingBox = null;
}
get boundingBox() {
if (this._boundingBox)
return this._boundingBox;
const oc = getOC();
const boundBox = new oc.Bnd_Box2d();
oc.BndLib_Add2dCurve.Add_3(this.wrapped, 1e-6, boundBox);
this._boundingBox = new BoundingBox2d(boundBox);
return this._boundingBox;
}
get repr() {
return `${this.geomType} ${reprPnt(this.firstPoint)} - ${reprPnt(
this.lastPoint
)}`;
}
get innerCurve() {
return this.wrapped.get();
}
value(parameter) {
const pnt2 = this.innerCurve.Value(parameter);
const vec2 = [pnt2.X(), pnt2.Y()];
pnt2.delete();
return vec2;
}
get firstPoint() {
return this.value(this.firstParameter);
}
get lastPoint() {
return this.value(this.lastParameter);
}
get firstParameter() {
return this.innerCurve.FirstParameter();
}
get lastParameter() {
return this.innerCurve.LastParameter();
}
adaptor() {
const oc = getOC();
return new oc.Geom2dAdaptor_Curve_2(this.wrapped);
}
get geomType() {
const adaptor = this.adaptor();
const curveType = findCurveType(adaptor.GetType());
adaptor.delete();
return curveType;
}
clone() {
return new Curve2D(this.innerCurve.Copy());
}
reverse() {
this.innerCurve.Reverse();
}
distanceFromPoint(point) {
const oc = getOC();
const r = GCWithScope();
const projector = r(
new oc.Geom2dAPI_ProjectPointOnCurve_2(r(pnt(point)), this.wrapped)
);
let curveToPoint = Infinity;
try {
curveToPoint = projector.LowerDistance();
} catch (e) {
curveToPoint = Infinity;
}
return Math.min(
curveToPoint,
distance2d(point, this.firstPoint),
distance2d(point, this.lastPoint)
);
}
distanceFromCurve(curve) {
const oc = getOC();
const r = GCWithScope();
let curveDistance = Infinity;
const projector = r(
new oc.Geom2dAPI_ExtremaCurveCurve(
this.wrapped,
curve.wrapped,
this.firstParameter,
this.lastParameter,
curve.firstParameter,
curve.lastParameter
)
);
try {
curveDistance = projector.LowerDistance();
} catch (e) {
curveDistance = Infinity;
}
return Math.min(
curveDistance,
this.distanceFromPoint(curve.firstPoint),
this.distanceFromPoint(curve.lastPoint),
curve.distanceFromPoint(this.firstPoint),
curve.distanceFromPoint(this.lastPoint)
);
}
distanceFrom(element) {
if (isPoint2D(element)) {
return this.distanceFromPoint(element);
}
return this.distanceFromCurve(element);
}
isOnCurve(point) {
return this.distanceFromPoint(point) < 1e-9;
}
parameter(point, precision = 1e-9) {
const oc = getOC();
const r = GCWithScope();
let lowerDistance;
let lowerDistanceParameter;
try {
const projector = r(
new oc.Geom2dAPI_ProjectPointOnCurve_2(r(pnt(point)), this.wrapped)
);
lowerDistance = projector.LowerDistance();
lowerDistanceParameter = projector.LowerDistanceParameter();
} catch (e) {
if (samePoint$2(point, this.firstPoint, precision))
return this.firstParameter;
if (samePoint$2(point, this.lastPoint, precision))
return this.lastParameter;
throw new Error("Failed to find parameter");
}
if (lowerDistance > precision) {
throw new Error(
`Point ${reprPnt(point)} not on curve ${this.repr}, ${lowerDistance.toFixed(9)}`
);
}
return lowerDistanceParameter;
}
tangentAt(index) {
const oc = getOC();
const [r, gc] = localGC();
let param;
if (Array.isArray(index)) {
param = this.parameter(index);
} else {
const paramLength = this.innerCurve.LastParameter() - this.innerCurve.FirstParameter();
param = paramLength * index + this.innerCurve.FirstParameter();
}
const point = r(new oc.gp_Pnt2d_1());
const dir = r(new oc.gp_Vec2d_1());
this.innerCurve.D1(param, point, dir);
const tgtVec = [dir.X(), dir.Y()];
gc();
return tgtVec;
}
splitAt(points, precision = 1e-9) {
const oc = getOC();
const r = GCWithScope();
let parameters = points.map((point) => {
if (isPoint2D(point))
return this.parameter(point, precision);
return point;
});
parameters = Array.from(
new Map(
parameters.map((p) => [precisionRound(p, -Math.log10(precision)), p])
).values()
).sort((a, b) => a - b);
const firstParam = this.firstParameter;
const lastParam = this.lastParameter;
if (firstParam > lastParam) {
parameters.reverse();
}
if (Math.abs(parameters[0] - firstParam) < precision * 100)
parameters = parameters.slice(1);
if (!parameters.length)
return [this];
if (Math.abs(parameters[parameters.length - 1] - lastParam) < precision * 100)
parameters = parameters.slice(0, -1);
if (!parameters.length)
return [this];
return zip([
[firstParam, ...parameters],
[...parameters, lastParam]
]).map(([first, last]) => {
try {
if (this.geomType === "BEZIER_CURVE") {
const curveCopy = new oc.Geom2d_BezierCurve_1(
r(this.adaptor()).Bezier().get().Poles_2()
);
curveCopy.Segment(first, last);
return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));
}
if (this.geomType === "BSPLINE_CURVE") {
const adapted = r(this.adaptor()).BSpline().get();
const curveCopy = new oc.Geom2d_BSplineCurve_1(
adapted.Poles_2(),
adapted.Knots_2(),
adapted.Multiplicities_2(),
adapted.Degree(),
adapted.IsPeriodic()
);
curveCopy.Segment(first, last, precision);
return new Curve2D(new oc.Handle_Geom2d_Curve_2(curveCopy));
}
const trimmed = new oc.Geom2d_TrimmedCurve(
this.wrapped,
first,
last,
true,
true
);
return new Curve2D(new oc.Handle_Geom2d_Curve_2(trimmed));
} catch (e) {
throw new Error("Failed to split the curve");
}
});
}
}
const approximateAsBSpline = (adaptor, tolerance = 1e-4, continuity = "C0", maxSegments = 200) => {
const oc = getOC();
const r = GCWithScope();
const continuities = {
C0: oc.GeomAbs_Shape.GeomAbs_C0,
C1: oc.GeomAbs_Shape.GeomAbs_C1,
C2: oc.GeomAbs_Shape.GeomAbs_C2,
C3: oc.GeomAbs_Shape.GeomAbs_C3
};
const convert = r(
new oc.Geom2dConvert_ApproxCurve_2(
adaptor.ShallowCopy(),
tolerance,
continuities[continuity],
maxSegments,
3
)
);
return new Curve2D(convert.Curve());
};
const BSplineToBezier = (adaptor) => {
if (findCurveType(adaptor.GetType()) !== "BSPLINE_CURVE")
throw new Error("You can only convert a Bspline");
const handle = adaptor.BSpline();
const oc = getOC();
const convert = new oc.Geom2dConvert_BSplineCurveToBezierCurve_1(handle);
function* bezierCurves() {
const nArcs = convert.NbArcs();
if (!nArcs)
return;
for (let i = 1; i <= nArcs; i++) {
const arc = convert.Arc(i);
yield new Curve2D(arc);
}
}
const curves = Array.from(bezierCurves());
convert.delete();
return curves;
};
function approximateAsSvgCompatibleCurve(curves, options = {
tolerance: 1e-4,
continuity: "C0",
maxSegments: 300
}) {
const r = GCWithScope();
return curves.flatMap((curve) => {
const adaptor = r(curve.adaptor());
const curveType = findCurveType(adaptor.GetType());
if (curveType === "ELLIPSE" || curveType === "CIRCLE" && samePoint$2(curve.firstPoint, curve.lastPoint)) {
return curve.splitAt([0.5]);
}
if (["LINE", "ELLIPSE", "CIRCLE"].includes(curveType)) {
return curve;
}
if (curveType === "BEZIER_CURVE") {
const b = adaptor.Bezier().get();
const deg = b.Degree();
if ([1, 2, 3].includes(deg)) {
return curve;
}
}
if (curveType === "BSPLINE_CURVE") {
const c = BSplineToBezier(adaptor);
return approximateAsSvgCompatibleCurve(c, options);
}
const bspline = approximateAsBSpline(
adaptor,
options.tolerance,
options.continuity,
options.maxSegments
);
return approximateAsSvgCompatibleCurve(
BSplineToBezier(r(bspline.adaptor())),
options
);
});
}
function* pointsIteration(intersector) {
const nPoints = intersector.NbPoints();
if (!nPoints)
return;
for (let i = 1; i <= nPoints; i++) {
const point = intersector.Point(i);
yield [point.X(), point.Y()];
}
}
function* commonSegmentsIteration(intersector) {
const nSegments = intersector.NbSegments();
if (!nSegments)
return;
const oc = getOC();
for (let i = 1; i <= nSegments; i++) {
const h1 = new oc.Handle_Geom2d_Curve_1();
const h2 = new oc.Handle_Geom2d_Curve_1();
try {
intersector.Segment(i, h1, h2);
} catch (e) {
continue;
}
yield new Curve2D(h1);
h2.delete();
}
}
const intersectCurves = (first, second, precision = 1e-9) => {
if (first.boundingBox.isOut(second.boundingBox))
return { intersections: [], commonSegments: [], commonSegmentsPoints: [] };
const oc = getOC();
const intersector = new oc.Geom2dAPI_InterCurveCurve_1();
let intersections;
let commonSegments;
try {
intersector.Init_1(first.wrapped, second.wrapped, precision);
intersections = Array.from(pointsIteration(intersector));
commonSegments = Array.from(commonSegmentsIteration(intersector));
} catch (e) {
console.error(first, second, e);
throw new Error("Intersections failed between curves");
} finally {
intersector.delete();
}
const segmentsAsPoints = commonSegments.filter((c) => samePoint$2(c.firstPoint, c.lastPoint, precision)).map((c) => c.firstPoint);
if (segmentsAsPoints.length) {
intersections.push(...segmentsAsPoints);
commonSegments = commonSegments.filter(
(c) => !samePoint$2(c.firstPoint, c.lastPoint, precision)
);
}
const commonSegmentsPoints = commonSegments.flatMap((c) => [
c.firstPoint,
c.lastPoint
]);
return { intersections, commonSegments, commonSegmentsPoints };
};
const selfIntersections = (curve, precision = 1e-9) => {
const oc = getOC();
const intersector = new oc.Geom2dAPI_InterCurveCurve_1();
let intersections;
try {
intersector.Init_1(curve.wrapped, curve.wrapped, precision);
intersections = Array.from(pointsIteration(intersector));
} catch (e) {
throw new Error("Self intersection failed");
} finally {
intersector.delete();
}
return intersections;
};
const make2dSegmentCurve = (startPoint, endPoint) => {
const oc = getOC();
const [r, gc] = localGC();
const segment = r(
new oc.GCE2d_MakeSegment_1(r(pnt(startPoint)), r(pnt(endPoint)))
).Value();
const curve = new Curve2D(segment);
if (!samePoint$2(curve.firstPoint, startPoint)) {
curve.reverse();
}
gc();
return curve;
};
const make2dThreePointArc = (startPoint, midPoint, endPoint) => {
const oc = getOC();
const [r, gc] = localGC();
const segment = r(
new oc.GCE2d_MakeArcOfCircle_4(
r(pnt(startPoint)),
r(pnt(midPoint)),
r(pnt(endPoint))
)
).Value();
gc();
const curve = new Curve2D(segment);
if (!samePoint$2(curve.firstPoint, startPoint)) {
curve.wrapped.get().SetTrim(
curve.lastParameter,
curve.firstParameter,
true,
true
);
}
return curve;
};
const make2dTangentArc = (startPoint, tangent, endPoint) => {
const oc = getOC();
const [r, gc] = localGC();
const segment = r(
new oc.GCE2d_MakeArcOfCircle_5(
r(pnt(startPoint)),
r(vec(tangent)),
r(pnt(endPoint))
)
).Value();
gc();
const curve = new Curve2D(segment);
if (!samePoint$2(curve.firstPoint, startPoint)) {
curve.wrapped.get().SetTrim(
curve.lastParameter,
curve.firstParameter,
true,
true
);
}
return curve;
};
const make2dCircle = (radius, center = [0, 0]) => {
const oc = getOC();
const [r, gc] = localGC();
const segment = r(
new oc.GCE2d_MakeCircle_7(r(pnt(center)), radius, true)
).Value();
gc();
return new Curve2D(segment);
};
const make2dEllipse = (majorRadius, minorRadius, xDir = [1, 0], center = [0, 0], direct = true) => {
const oc = getOC();
const [r, gc] = localGC();
const ellipse = r(
new oc.gp_Elips2d_2(
r(axis2d(center, xDir)),
majorRadius,
minorRadius,
direct
)
);
const segment = r(new oc.GCE2d_MakeEllipse_1(ellipse)).Value();
gc();
return new Curve2D(segment);
};
const make2dEllipseArc = (majorRadius, minorRadius, startAngle, endAngle, center = [0, 0], xDir, direct = true) => {
const oc = getOC();
const [r, gc] = localGC();
const ellipse = r(
new oc.gp_Elips2d_2(r(axis2d(center, xDir)), majorRadius, minorRadius, true)
);
const segment = r(
new oc.GCE2d_MakeArcOfEllipse_1(ellipse, startAngle, endAngle, direct)
).Value();
gc();
return new Curve2D(segment);
};
const make2dBezierCurve = (startPoint, controls, endPoint) => {
const oc = getOC();
const [r, gc] = localGC();
const arrayOfPoints = r(
new oc.TColgp_Array1OfPnt2d_2(1, controls.length + 2)
);
arrayOfPoints.SetValue(1, r(pnt(startPoint)));
controls.forEach((p, i) => {
arrayOfPoints.SetValue(i + 2, r(pnt(p)));
});
arrayOfPoints.SetValue(controls.length + 2, r(pnt(endPoint)));
const bezCurve = new oc.Geom2d_BezierCurve_1(arrayOfPoints);
gc();
return new Curve2D(new oc.Handle_Geom2d_Curve_2(bezCurve));
};
function make2dInerpolatedBSplineCurve(points, {
tolerance = 1e-3,
smoothing = null,
degMax = 3,
degMin = 1
} = {}) {
const r = GCWithScope();
const oc = getOC();
const pnts = r(new oc.TColgp_Array1OfPnt2d_2(1, points.length));
points.forEach((point, index) => {
pnts.SetValue(index + 1, r(pnt(point)));
});
let splineBuilder;
if (smoothing) {
splineBuilder = r(
new oc.Geom2dAPI_PointsToBSpline_6(
pnts,
smoothing[0],
smoothing[1],
smoothing[2],
degMax,
oc.GeomAbs_Shape.GeomAbs_C2,
tolerance
)
);
} else {
splineBuilder = r(
new oc.Geom2dAPI_PointsToBSpline_2(
pnts,
degMin,
degMax,
oc.GeomAbs_Shape.GeomAbs_C2,
tolerance
)
);
}
if (!splineBuilder.IsDone()) {
throw new Error("B-spline approximation failed");
}
return new Curve2D(splineBuilder.Curve());
}
const make2dArcFromCenter = (startPoint, endPoint, center) => {
const midChord = scalarMultiply2d(add2d(startPoint, endPoint), 0.5);
const orientedRadius = distance2d(center, startPoint);
const midChordDir = normalize2d(subtract2d(midChord, center));
return make2dThreePointArc(
startPoint,
add2d(scalarMultiply2d(midChordDir, orientedRadius), center),
endPoint
);
};
const offsetEndPoints = (firstPoint, lastPoint, offset2) => {
const tangent = normalize2d(subtract2d(lastPoint, firstPoint));
const normal = [tangent[1], -tangent[0]];
const offsetVec = [normal[0] * offset2, normal[1] * offset2];
return {
firstPoint: add2d(firstPoint, offsetVec),
lastPoint: add2d(lastPoint, offsetVec)
};
};
const make2dOffset = (curve, offset2) => {
const r = GCWithScope();
const curveType = curve.geomType;
if (curveType === "CIRCLE") {
const circle = r(r(curve.adaptor()).Circle());
const radius = circle.Radius();
const orientationCorrection = circle.IsDirect() ? 1 : -1;
const orientedOffset = offset2 * orientationCorrection;
const newRadius = radius + orientedOffset;
if (newRadius < 1e-10) {
const centerPos = r(circle.Location());
const center = [centerPos.X(), centerPos.Y()];
const offsetViaCenter = (point) => {
const [x, y] = normalize2d(subtract2d(point, center));
return add2d(point, [orientedOffset * x, orientedOffset * y]);
};
return {
collapsed: true,
firstPoint: offsetViaCenter(curve.firstPoint),
lastPoint: offsetViaCenter(curve.lastPoint)
};
}
const oc2 = getOC();
const newCircle = new oc2.gp_Circ2d_3(circle.Axis(), newRadius);
const newInnerCurve = new oc2.Geom2d_Circle_1(newCircle);
const newCurve = new oc2.Geom2d_TrimmedCurve(
new oc2.Handle_Geom2d_Curve_2(newInnerCurve),
curve.firstParameter,
curve.lastParameter,
true,
true
);
return new Curve2D(new oc2.Handle_Geom2d_Curve_2(newCurve));
}
if (curveType === "LINE") {
const { firstPoint, lastPoint } = offsetEndPoints(
curve.firstPoint,
curve.lastPoint,
offset2
);
return make2dSegmentCurve(firstPoint, lastPoint);
}
const oc = getOC();
const offsetCurve = new Curve2D(
new oc.Handle_Geom2d_Curve_2(
new oc.Geom2d_OffsetCurve(curve.wrapped, offset2, true)
)
);
const approximation = approximateAsBSpline(offsetCurve.adaptor());
const selfIntersects = selfIntersections(approximation);
if (selfIntersects.length) {
return {
collapsed: true,
firstPoint: approximation.firstPoint,
lastPoint: approximation.lastPoint
};
}
return approximation;
};
function removeCorner(firstCurve, secondCurve, radius) {
const sinAngle = crossProduct2d(
firstCurve.tangentAt(1),
secondCurve.tangentAt(0)
);
if (Math.abs(sinAngle) < 1e-10)
return null;
const orientationCorrection = sinAngle > 0 ? -1 : 1;
const offset2 = Math.abs(radius) * orientationCorrection;
const firstOffset = make2dOffset(firstCurve, offset2);
const secondOffset = make2dOffset(secondCurve, offset2);
if (!(firstOffset instanceof Curve2D) || !(secondOffset instanceof Curve2D)) {
return null;
}
let potentialCenter;
try {
const { intersections } = intersectCurves(firstOffset, secondOffset, 1e-9);
potentialCenter = intersections.at(-1);
} catch (e) {
return null;
}
if (!isPoint2D(potentialCenter)) {
return null;
}
const center = potentialCenter;
const splitForFillet = (curve, offsetCurve) => {
const [x, y] = offsetCurve.tangentAt(center);
const normal = normalize2d([-y, x]);
const splitPoint = add2d(center, scalarMultiply2d(normal, offset2));
const splitParam = curve.parameter(splitPoint, 1e-6);
return curve.splitAt([splitParam]);
};
const [first] = splitForFillet(firstCurve, firstOffset);
const [, second] = splitForFillet(secondCurve, secondOffset);
return { first, second, center };
}
function filletCurves(firstCurve, secondCurve, radius) {
const cornerRemoved = removeCorner(firstCurve, secondCurve, radius);
if (!cornerRemoved) {
console.warn(
"Cannot fillet between curves",
firstCurve.repr,
secondCurve.repr
);
return [firstCurve, secondCurve];
}
const { first, second, center } = cornerRemoved;
return [
first,
make2dArcFromCenter(first.lastPoint, second.firstPoint, center),
second
];
}
function chamferCurves(firstCurve, secondCurve, radius) {
const cornerRemoved = removeCorner(firstCurve, secondCurve, radius);
if (!cornerRemoved) {
console.warn(
"Cannot chamfer between curves",
firstCurve.repr,
secondCurve.repr
);
return [firstCurve, secondCurve];
}
const { first, second } = cornerRemoved;
return [
first,
make2dSegmentCurve(first.lastPoint, second.firstPoint),
second
];
}
class FlatQueue {
constructor() {
this.ids = [];
this.values = [];
this.length = 0;
}
clear() {
this.length = 0;
}
push(id, value) {
let pos = this.length++;
while (pos > 0) {
const parent = pos - 1 >> 1;
const parentValue = this.values[parent];
if (value >= parentValue)
break;
this.ids[pos] = this.ids[parent];
this.values[pos] = parentValue;
pos = parent;
}
this.ids[pos] = id;
this.values[pos] = value;
}
pop() {
if (this.length === 0)
return void 0;
const top = this.ids[0];
this.length--;
if (this.length > 0) {
const id = this.ids[0] = this.ids[this.length];
const value = this.values[0] = this.values[this.length];
const halfLength = this.length >> 1;
let pos = 0;
while (pos < halfLength) {
let left = (pos << 1) + 1;
const right = left + 1;
let bestIndex = this.ids[left];
let bestValue = this.values[left];
const rightValue = this.values[right];
if (right < this.length && rightValue < bestValue) {
left = right;
bestIndex = this.ids[right];
bestValue = rightValue;
}
if (bestValue >= value)
break;
this.ids[pos] = bestIndex;
this.values[pos] = bestValue;
pos = left;
}
this.ids[pos] = id;
this.values[pos] = value;
}
return top;
}
peek() {
if (this.length === 0)
return void 0;
return this.ids[0];
}
peekValue() {
if (this.length === 0)
return void 0;
return this.values[0];
}
shrink() {
this.ids.length = this.values.length = this.length;
}
}
const ARRAY_TYPES = [
Int8Array,
Uint8Array,
Uint8ClampedArray,
Int16Array,
Uint16Array,
Int32Array,
Uint32Array,
Float32Array,
Float64Array
];
const VERSION = 3;
class Flatbush {
static from(data) {
if (!(data instanceof ArrayBuffer)) {
throw new Error("Data must be an instance of ArrayBuffer.");
}
const [magic, versionAndType] = new Uint8Array(data, 0, 2);
if (magic !== 251) {
throw new Error("Data does not appear to be in a Flatbush format.");
}
if (versionAndType >> 4 !== VERSION) {
throw new Error(`Got v${versionAndType >> 4} data when expected v${VERSION}.`);
}
const [nodeSize] = new Uint16Array(data, 2, 1);
const [numItems] = new Uint32Array(data, 4, 1);
return new Flatbush(numItems, nodeSize, ARRAY_TYPES[versionAndType & 15], data);
}
constructor(numItems, nodeSize = 16, ArrayType = Float64Array, data) {
if (numItems === void 0)
throw new Error("Missing required argument: numItems.");
if (isNaN(numItems) || numItems <= 0)
throw new Error(`Unpexpected numItems value: ${numItems}.`);
this.numItems = +numItems;
this.nodeSize = Math.min(Math.max(+nodeSize, 2), 65535);
let n = numItems;
let numNodes = n;
this._levelBounds = [n * 4];
do {
n = Math.ceil(n / this.nodeSize);
numNodes += n;
this._levelBounds.push(numNodes * 4);
} while (n !== 1);
this.ArrayType = ArrayType || Float64Array;
this.IndexArrayType = numNodes < 16384 ? Uint16Array : Uint32Array;
const arrayTypeIndex = ARRAY_TYPES.indexOf(this.ArrayType);
const nodesByteSize = numNodes * 4 * this.ArrayType.BYTES_PER_ELEMENT;
if (arrayTypeIndex < 0) {
throw new Error(`Unexpected typed array class: ${ArrayType}.`);
}
if (data && data instanceof ArrayBuffer) {
this.data = data;
this._boxes = new this.ArrayType(this.data, 8, numNodes * 4);
this._indices = new this.IndexArrayType(this.data, 8 + nodesByteSize, numNodes);
this._pos = numNodes * 4;
this.minX = this._boxes[this._pos - 4];
this.minY = this._boxes[this._pos - 3];
this.maxX = this._boxes[this._pos - 2];
this.maxY = this._boxes[this._pos - 1];
} else {
this.data = new ArrayBuffer(8 + nodesByteSize + numNodes * this.IndexArrayType.BYTES_PER_ELEMENT);
this._boxes = new this.ArrayType(this.data, 8, numNodes * 4);
this._indices = new this.IndexArrayType(this.data, 8 + nodesByteSize, numNodes);
this._pos = 0;
this.minX = Infinity;
this.minY = Infinity;
this.maxX = -Infinity;
this.maxY = -Infinity;
new Uint8Array(this.data, 0, 2).set([251, (VERSION << 4) + arrayTypeIndex]);
new Uint16Array(this.data, 2, 1)[0] = nodeSize;
new Uint32Array(this.data, 4, 1)[0] = numItems;
}
this._queue = new FlatQueue();
}
add(minX, minY, maxX, maxY) {
const index = this._pos >> 2;
this._indices[index] = index;
this._boxes[this._pos++] = minX;
this._boxes[this._pos++] = minY;
this._boxes[this._pos++] = maxX;
this._boxes[this._pos++] = maxY;
if (minX < this.minX)
this.minX = minX;
if (minY < this.minY)
this.minY = minY;
if (maxX > this.maxX)
this.maxX = maxX;
if (maxY > this.maxY)
this.maxY = maxY;
return index;
}
finish() {
if (this._pos >> 2 !== this.numItems) {
throw new Error(`Added ${this._pos >> 2} items when expected ${this.numItems}.`);
}
if (this.numItems <= this.nodeSize) {
this._boxes[this._pos++] = this.minX;
this._boxes[this._pos++] = this.minY;
this._boxes[this._pos++] = this.maxX;
this._boxes[this._pos++] = this.maxY;
return;
}
const width = this.maxX - this.minX || 1;
const height = this.maxY - this.minY || 1;
const hilbertValues = new Uint32Array(this.numItems);
const hilbertMax = (1 << 16) - 1;
for (let i = 0; i < this.numItems; i++) {
let pos = 4 * i;
const minX = this._boxes[pos++];
const minY = this._boxes[pos++];
const maxX = this._boxes[pos++];
const maxY = this._boxes[pos++];
const x = Math.floor(hilbertMax * ((minX + maxX) / 2 - this.minX) / width);
const y = Math.floor(hilbertMax * ((minY + maxY) / 2 - this.mi