diff --git a/packages/chili-core/src/math/boundingBox.ts b/packages/chili-core/src/math/boundingBox.ts new file mode 100644 index 00000000..e3e91f92 --- /dev/null +++ b/packages/chili-core/src/math/boundingBox.ts @@ -0,0 +1,55 @@ +// Copyright 2022-2023 the Chili authors. All rights reserved. AGPL-3.0 license. + +export type PointLike = { x: number; y: number; z: number }; + +export class BoundingBox { + public min: PointLike; + public max: PointLike; + + constructor(min: PointLike, max: PointLike) { + this.min = min; + this.max = max; + } + + static expandByPoint(box: BoundingBox, point: PointLike) { + box.min.x = Math.min(box.min.x, point.x); + box.min.y = Math.min(box.min.y, point.y); + box.min.z = Math.min(box.min.z, point.z); + + box.max.x = Math.max(box.max.x, point.x); + box.max.y = Math.max(box.max.y, point.y); + box.max.z = Math.max(box.max.z, point.z); + } + + public static fromNumbers(points: number[]): BoundingBox { + let min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE, z: Number.MAX_VALUE }; + let max = { x: Number.MIN_VALUE, y: Number.MIN_VALUE, z: Number.MIN_VALUE }; + + for (let i = 0; i < points.length; i += 3) { + min.x = Math.min(min.x, points[i]); + min.y = Math.min(min.y, points[i + 1]); + min.z = Math.min(min.z, points[i + 2]); + + max.x = Math.max(max.x, points[i]); + max.y = Math.max(max.y, points[i + 1]); + max.z = Math.max(max.z, points[i + 2]); + } + return new BoundingBox(min, max); + } + + public static fromPoints(points: PointLike[]): BoundingBox { + let min = { x: Number.MAX_VALUE, y: Number.MAX_VALUE, z: Number.MAX_VALUE }; + let max = { x: Number.MIN_VALUE, y: Number.MIN_VALUE, z: Number.MIN_VALUE }; + for (let point of points) { + min.x = Math.min(min.x, point.x); + min.y = Math.min(min.y, point.y); + min.z = Math.min(min.z, point.z); + + max.x = Math.max(max.x, point.x); + max.y = Math.max(max.y, point.y); + max.z = Math.max(max.z, point.z); + } + + return new BoundingBox(min, max); + } +} diff --git a/packages/chili-core/src/model/node.ts b/packages/chili-core/src/model/node.ts index 249e1616..a72fb603 100644 --- a/packages/chili-core/src/model/node.ts +++ b/packages/chili-core/src/model/node.ts @@ -9,9 +9,11 @@ import { Id, PubSub, Result, + debounce, } from "../foundation"; import { I18n, I18nKeys } from "../i18n"; import { Matrix4 } from "../math"; +import { BoundingBox } from "../math/boundingBox"; import { Property } from "../property"; import { Serialized, Serializer } from "../serialize"; import { IShape, IShapeMeshData } from "../shape"; @@ -223,6 +225,7 @@ export namespace INode { export interface IMeshObject extends IPropertyChanged { materialId: string; matrix: Matrix4; + boundingBox(): BoundingBox; get mesh(): IShapeMeshData; } @@ -246,6 +249,7 @@ export abstract class GeometryNode extends Node implements IMeshObject { value, (_p, oldMatrix) => { this.onMatrixChanged(value, oldMatrix); + this._boundingBox = undefined; }, { equals: (left, right) => left.equals(right), @@ -258,6 +262,16 @@ export abstract class GeometryNode extends Node implements IMeshObject { this.setPrivateValue("materialId", materialId ?? document.materials.at(0)?.id ?? ""); } + protected _boundingBox: BoundingBox | undefined; + boundingBox(): BoundingBox { + if (this._boundingBox === undefined) { + let points = this.mesh.faces?.positions ?? this.mesh.edges?.positions ?? []; + points = this.matrix.ofPoints(points); + this._boundingBox = BoundingBox.fromNumbers(points); + } + return this._boundingBox; + } + protected onMatrixChanged(newMatrix: Matrix4, oldMatrix: Matrix4): void {} protected onVisibleChanged(): void { @@ -280,7 +294,28 @@ export abstract class GeometryNode extends Node implements IMeshObject { } export abstract class ShapeNode extends GeometryNode { - abstract get shape(): Result; + protected _shape: Result = Result.err(SHAPE_UNDEFINED); + get shape(): Result { + return this._shape; + } + + protected setShape(shape: Result) { + if (this._shape.isOk && this._shape.isOk && this._shape.value.isEqual(shape.value)) { + return; + } + + if (!shape.isOk) { + PubSub.default.pub("displayError", shape.error); + return; + } + + let oldShape = this._shape; + this._shape = shape; + this._mesh = undefined; + this._boundingBox = undefined; + this._shape.value.matrix = this.matrix; + this.emitPropertyChanged("shape", oldShape); + } protected override onMatrixChanged(newMatrix: Matrix4): void { if (this.shape.isOk) this.shape.value.matrix = newMatrix; @@ -301,24 +336,12 @@ export abstract class ShapeNode extends GeometryNode { const SHAPE_UNDEFINED = "Shape not initialized"; export abstract class ParameterShapeNode extends ShapeNode { - protected _shape: Result = Result.err(SHAPE_UNDEFINED); override get shape(): Result { if (!this._shape.isOk && this._shape.error === SHAPE_UNDEFINED) { this._shape = this.generateShape(); } return this._shape; } - override set shape(shape: Result) { - if (shape.isOk && this._shape.isOk && this._shape.value.isEqual(shape.value)) { - return; - } - - let oldShape = this._shape; - this._shape = shape; - this._mesh = undefined; - this._shape.value.matrix = this.matrix; - this.emitPropertyChanged("shape", oldShape); - } protected setPropertyEmitShapeChanged( property: K, @@ -327,7 +350,7 @@ export abstract class ParameterShapeNode extends ShapeNode { equals?: IEqualityComparer | undefined, ): boolean { if (this.setProperty(property, newValue, onPropertyChanged, equals)) { - this.shape = this.generateShape(); + this.setShape(this.generateShape()); return true; } @@ -345,26 +368,12 @@ export abstract class ParameterShapeNode extends ShapeNode { @Serializer.register(["document", "name", "shape", "materialId", "id"]) export class EditableShapeNode extends ShapeNode { - protected _shape: Result; @Serializer.serialze() override get shape(): Result { return this._shape; } override set shape(shape: Result) { - if (this._shape.isOk && this._shape.unchecked() === shape.unchecked()) { - return; - } - - if (!shape.isOk) { - PubSub.default.pub("displayError", shape.error); - return; - } - - let oldShape = this._shape; - this._shape = shape; - this._mesh = undefined; - this._shape.value.matrix = this.matrix; - this.emitPropertyChanged("shape", oldShape); + this.setShape(shape); } constructor(document: IDocument, name: string, shape: IShape, materialId?: string, id?: string) { diff --git a/packages/chili/src/bodys/arc.ts b/packages/chili/src/bodys/arc.ts index 19a052e4..1033df0b 100644 --- a/packages/chili/src/bodys/arc.ts +++ b/packages/chili/src/bodys/arc.ts @@ -23,7 +23,7 @@ export class ArcNode extends ParameterShapeNode { return this.getPrivateValue("center"); } set center(center: XYZ) { - this.setProperty("center", center); + this.setPropertyEmitShapeChanged("center", center); } @Serializer.serialze() @@ -43,7 +43,7 @@ export class ArcNode extends ParameterShapeNode { return this.getPrivateValue("angle"); } set angle(value: number) { - this.setProperty("angle", value); + this.setPropertyEmitShapeChanged("angle", value); } constructor(document: IDocument, normal: XYZ, center: XYZ, start: XYZ, angle: number) { diff --git a/packages/chili/src/bodys/box.ts b/packages/chili/src/bodys/box.ts index 34fdf805..073528d5 100644 --- a/packages/chili/src/bodys/box.ts +++ b/packages/chili/src/bodys/box.ts @@ -23,7 +23,7 @@ export class BoxNode extends ParameterShapeNode { return this.getPrivateValue("dx"); } set dx(dx: number) { - this.setProperty("dx", dx); + this.setPropertyEmitShapeChanged("dx", dx); } @Serializer.serialze() @@ -32,7 +32,7 @@ export class BoxNode extends ParameterShapeNode { return this.getPrivateValue("dy"); } set dy(dy: number) { - this.setProperty("dy", dy); + this.setPropertyEmitShapeChanged("dy", dy); } @Serializer.serialze() @@ -41,7 +41,7 @@ export class BoxNode extends ParameterShapeNode { return this.getPrivateValue("dz"); } set dz(dz: number) { - this.setProperty("dz", dz); + this.setPropertyEmitShapeChanged("dz", dz); } @Serializer.serialze() diff --git a/packages/chili/src/bodys/circle.ts b/packages/chili/src/bodys/circle.ts index 9586d1da..1659a4c4 100644 --- a/packages/chili/src/bodys/circle.ts +++ b/packages/chili/src/bodys/circle.ts @@ -14,7 +14,7 @@ export class CircleNode extends FacebaseNode { return this.getPrivateValue("center"); } set center(center: XYZ) { - this.setProperty("center", center); + this.setPropertyEmitShapeChanged("center", center); } @Serializer.serialze() @@ -23,7 +23,7 @@ export class CircleNode extends FacebaseNode { return this.getPrivateValue("radius"); } set radius(radius: number) { - this.setProperty("radius", radius); + this.setPropertyEmitShapeChanged("radius", radius); } @Serializer.serialze() diff --git a/packages/chili/src/bodys/face.ts b/packages/chili/src/bodys/face.ts index ec3acf9f..2682e038 100644 --- a/packages/chili/src/bodys/face.ts +++ b/packages/chili/src/bodys/face.ts @@ -23,7 +23,7 @@ export class FaceNode extends ParameterShapeNode { return this.getPrivateValue("shapes"); } set shapes(values: IEdge[] | IWire[]) { - this.setProperty("shapes", values); + this.setPropertyEmitShapeChanged("shapes", values); } constructor(document: IDocument, shapes: IEdge[] | IWire[]) { diff --git a/packages/chili/src/bodys/fuse.ts b/packages/chili/src/bodys/fuse.ts index bbed7b0e..c152b54d 100644 --- a/packages/chili/src/bodys/fuse.ts +++ b/packages/chili/src/bodys/fuse.ts @@ -13,7 +13,7 @@ export class FuseNode extends ParameterShapeNode { return this.getPrivateValue("bottom"); } set bottom(value: IShape) { - this.setProperty("bottom", value); + this.setPropertyEmitShapeChanged("bottom", value); } @Serializer.serialze() @@ -21,7 +21,7 @@ export class FuseNode extends ParameterShapeNode { return this.getPrivateValue("top"); } set top(value: IShape) { - this.setProperty("top", value); + this.setPropertyEmitShapeChanged("top", value); } constructor(document: IDocument, bottom: IShape, top: IShape) { diff --git a/packages/chili/src/bodys/line.ts b/packages/chili/src/bodys/line.ts index ac940185..65fc3945 100644 --- a/packages/chili/src/bodys/line.ts +++ b/packages/chili/src/bodys/line.ts @@ -23,7 +23,7 @@ export class LineNode extends ParameterShapeNode { return this.getPrivateValue("start"); } set start(pnt: XYZ) { - this.setProperty("start", pnt); + this.setPropertyEmitShapeChanged("start", pnt); } @Serializer.serialze() @@ -32,7 +32,7 @@ export class LineNode extends ParameterShapeNode { return this.getPrivateValue("end"); } set end(pnt: XYZ) { - this.setProperty("end", pnt); + this.setPropertyEmitShapeChanged("end", pnt); } constructor(document: IDocument, start: XYZ, end: XYZ) { diff --git a/packages/chili/src/bodys/polygon.ts b/packages/chili/src/bodys/polygon.ts index 02a96fab..11e32bf9 100644 --- a/packages/chili/src/bodys/polygon.ts +++ b/packages/chili/src/bodys/polygon.ts @@ -14,7 +14,7 @@ export class PolygonNode extends FacebaseNode { return this.getPrivateValue("points"); } set points(value: XYZ[]) { - this.setProperty("points", value); + this.setPropertyEmitShapeChanged("points", value); } constructor(document: IDocument, points: XYZ[]) { diff --git a/packages/chili/src/bodys/prism.ts b/packages/chili/src/bodys/prism.ts index 7488354f..ee9b147a 100644 --- a/packages/chili/src/bodys/prism.ts +++ b/packages/chili/src/bodys/prism.ts @@ -23,7 +23,7 @@ export class PrismNode extends ParameterShapeNode { return this.getPrivateValue("section"); } set section(value: IShape) { - this.setProperty("section", value); + this.setPropertyEmitShapeChanged("section", value); } @Serializer.serialze() @@ -32,7 +32,7 @@ export class PrismNode extends ParameterShapeNode { return this.getPrivateValue("length"); } set length(value: number) { - this.setProperty("length", value); + this.setPropertyEmitShapeChanged("length", value); } constructor(document: IDocument, face: IShape, length: number) { diff --git a/packages/chili/src/bodys/rect.ts b/packages/chili/src/bodys/rect.ts index 36f0365f..2f231d03 100644 --- a/packages/chili/src/bodys/rect.ts +++ b/packages/chili/src/bodys/rect.ts @@ -24,7 +24,7 @@ export class RectNode extends FacebaseNode { return this.getPrivateValue("dx"); } set dx(dx: number) { - this.setProperty("dx", dx); + this.setPropertyEmitShapeChanged("dx", dx); } @Serializer.serialze() @@ -33,7 +33,7 @@ export class RectNode extends FacebaseNode { return this.getPrivateValue("dy"); } set dy(dy: number) { - this.setProperty("dy", dy); + this.setPropertyEmitShapeChanged("dy", dy); } @Serializer.serialze() diff --git a/packages/chili/src/bodys/revolve.ts b/packages/chili/src/bodys/revolve.ts index 654a1a35..8cfbe0cc 100644 --- a/packages/chili/src/bodys/revolve.ts +++ b/packages/chili/src/bodys/revolve.ts @@ -13,7 +13,7 @@ export class RevolvedNode extends ParameterShapeNode { return this.getPrivateValue("profile"); } set profile(value: IShape) { - this.setProperty("profile", value); + this.setPropertyEmitShapeChanged("profile", value); } @Serializer.serialze() @@ -21,7 +21,7 @@ export class RevolvedNode extends ParameterShapeNode { return this.getPrivateValue("axis"); } set axis(value: Ray) { - this.setProperty("axis", value); + this.setPropertyEmitShapeChanged("axis", value); } @Serializer.serialze() @@ -29,7 +29,7 @@ export class RevolvedNode extends ParameterShapeNode { return this.getPrivateValue("angle"); } set angle(value: number) { - this.setProperty("angle", value); + this.setPropertyEmitShapeChanged("angle", value); } constructor(document: IDocument, profile: IShape, axis: Ray, angle: number) { diff --git a/packages/chili/src/bodys/sweep.ts b/packages/chili/src/bodys/sweep.ts index 2ae48e7f..ee726322 100644 --- a/packages/chili/src/bodys/sweep.ts +++ b/packages/chili/src/bodys/sweep.ts @@ -23,7 +23,7 @@ export class SweepedNode extends ParameterShapeNode { return this.getPrivateValue("profile"); } set profile(value: IShape) { - this.setProperty("profile", value); + this.setPropertyEmitShapeChanged("profile", value); } @Serializer.serialze() @@ -31,7 +31,7 @@ export class SweepedNode extends ParameterShapeNode { return this.getPrivateValue("path"); } set path(value: IWire) { - this.setProperty("path", value); + this.setPropertyEmitShapeChanged("path", value); } constructor(document: IDocument, profile: IShape, path: IWire | IEdge) { diff --git a/packages/chili/src/bodys/wire.ts b/packages/chili/src/bodys/wire.ts index 713bffe0..0865ecb5 100644 --- a/packages/chili/src/bodys/wire.ts +++ b/packages/chili/src/bodys/wire.ts @@ -13,7 +13,7 @@ export class WireNode extends ParameterShapeNode { return this.getPrivateValue("edges"); } set edges(values: IEdge[]) { - this.setProperty("edges", values); + this.setPropertyEmitShapeChanged("edges", values); } constructor(document: IDocument, edges: IEdge[]) {