import { Group, Box3, BufferGeometry, Mesh, Vector3, Plane, Raycaster, Quaternion, Object3D, Matrix4, Vector2, Face, PlaneGeometry } from 'three'; import { TransformControls } from '../TransformControls.js'; import { OBB } from 'three/examples/jsm/math/OBB.js'; import { type IViewer } from '../../../IViewer.js'; import { CameraController } from '../CameraController.js'; import { Vector3Like } from '../../batching/BatchObject.js'; import { LineSegments2 } from 'three/examples/jsm/lines/LineSegments2.js'; import SpeckleStandardMaterial from '../../materials/SpeckleStandardMaterial.js'; import { Extension } from '../Extension.js'; export declare enum SectionToolEvent { DragStart = "section-box-drag-start", DragEnd = "section-box-drag-end", Updated = "section-box-changed" } export interface SectionToolEventPayload { [SectionToolEvent.DragStart]: void; [SectionToolEvent.DragEnd]: void; [SectionToolEvent.Updated]: Plane[]; } export declare class SectionTool extends Extension { protected cameraProvider: CameraController; get inject(): (typeof CameraController)[]; /** Configurable rotation snap angle in radians. Set to null to disable snapping */ rotationSnapAngle: number | null; /** Note: Rotation snapping only applies to mouse interactions via TransformControls. * Programmatic calls to setBox() will not apply rotation snapping. * For complete snapping support, programmatic rotations will need to be handled separately. */ /** This is our data model. All we need is an OBB */ protected obb: OBB; /** The planes that will send out as clipping planes */ protected planes: Plane[]; /** The six planes of the unit cube */ protected localPlanes: Plane[]; /** Convenience LUT for when clicking on the box faces */ protected cubeFaces: { '256': { verts: number[]; axis: string; normal: Vector3; }; '152': { verts: number[]; axis: string; normal: Vector3; }; '407': { verts: number[]; axis: string; normal: Vector3; }; '703': { verts: number[]; axis: string; normal: Vector3; }; '327': { verts: number[]; axis: string; normal: Vector3; }; '726': { verts: number[]; axis: string; normal: Vector3; }; '450': { verts: number[]; axis: string; normal: Vector3; }; '051': { verts: number[]; axis: string; normal: Vector3; }; '312': { verts: number[]; axis: string; normal: Vector3; }; '013': { verts: number[]; axis: string; normal: Vector3; }; '546': { verts: number[]; axis: string; normal: Vector3; }; '647': { verts: number[]; axis: string; normal: Vector3; }; }; /** The root object */ protected display: Group; /** We only use this for hit testing to select it's faces */ protected boxHitMesh: Mesh; /** The displayed box as an outline */ protected boxOutline: LineSegments2; /** Mesh that gets shown across a box face when it gets selected */ protected facePlane: Mesh; /** Anchor objects for the controls. Not displayable */ protected translationRotationAnchor: Object3D; protected scaleAnchor: Object3D; /** Controls instances */ protected translateControls: TransformControls; protected rotateControls: TransformControls; protected scaleControls: TransformControls; /** Sum state */ protected lastScale: Vector3 | null; protected lastObbTransform: Matrix4; protected draggingFace: Face | null; /** Hit testing related */ protected raycaster: Raycaster; protected dragging: boolean; protected shiftKeyPressed: boolean; protected keydownHandler: (e: KeyboardEvent) => void; protected keyupHandler: (e: KeyboardEvent) => void; protected sectionBoxHistory: OBB[]; protected currentHistoryIndex: number; protected maxHistorySize: number; /** Manadatory property for all extensions */ get enabled(): boolean; set enabled(value: boolean); /** Hides all controls and the box outline but keeps the sections enabled */ get visible(): boolean; set visible(value: boolean); /** Gets the up to date section planes */ get sectionPlanes(): Plane[]; /** * */ constructor(viewer: IViewer, cameraProvider: CameraController); /** * */ /** We use the viewer's frame update to enable/disable the translate and rotate controls * The controls were not meant to be overlayed and working in multiple instance setups * so their input events overlap. It's not the best solution in the world, but it's one * that requires little to no source change */ onEarlyUpdate(): void; /** Explicit events for the selection tool */ on(eventType: T, listener: (arg: SectionToolEventPayload[T]) => void): void; /** Gets the OBB model */ getBox(): OBB; /** * Sets the OBB model and updates the tool gizmos and box * @param targetBox The box to set. It accepts an aabb as well * @param offset Optional offset */ setBox(targetBox: OBB | Box3 | { min: Vector3Like; max: Vector3Like; }, offset?: number): void; /** * Convenience method */ toggle(): void; /** Gets the transformation defined by the OBB */ getObbTransform(): Matrix4; /** * Creates the controls and all their gizmos */ protected setupControls(): void; /** * Creates an OBB state from the current OBB */ protected createObbState(): OBB; /** * Applies an OBB state to the current OBB */ protected applyObbState(state: OBB): void; /** * Saves the current section box state to history */ protected saveToHistory(): void; /** * Sets up keyboard event listeners for shift key rotation snapping and undo/redo */ protected setupKeyboardListeners(): void; /** * Controls, outline and hitbox update based on the OBB model */ protected updateVisual(): void; /** * Triggers when transform interactions start/stop * @param event Controls event */ protected draggingHandler(event: any): void; /** Triggers whenever there is a change in the controls and their gizmos * This is where the model OBB gets updated according to controls changes. * Each control type writes it's data in the anchor's matching property. So: * - Translate control write to it's anchor 'position' * - Rotate control writes to it's anchor 'quaternion' * - Scale control writes to it's anchor 'scale */ protected changeHandler(): void; /** * Handler used for detecting when we click on the box faces * @param args NDC pointer coords + original event + multiselect * @returns */ protected clickHandler(args: Vector2 & { event: PointerEvent; multiSelect: boolean; }): void; /** * Computes the final box planes from the unit cube planes * transformed against the current OBB model */ protected updatePlanes(): void; /** * Creates the plane mesh that will be shown when selecting a box face * It's RTE enabled */ protected createFacePlane(): Mesh; /** * Updates the scale control and plane mesh when a cube face is selected * @param face The face we're pulling/pushing * @returns void */ protected updateFaceControls(face: Face | null): void; /** Creates the geometry for the visible outline of the section tool */ protected createOutline(): LineSegments2; /** Updates the section tool outline by updating it's vertices * We chose to do it this way in order have RTE working */ protected updateOutline(): void; /** Creates a unit cube geometry instance*/ protected createCubeGeometry(): BufferGeometry; /** Restes the gizmos. Honestly not sure if it does anything meaningfull */ protected reset(): void; /** Type guards */ protected isAABB(box: Box3 | { min: Vector3Like; max: Vector3Like; } | OBB): box is Box3; protected isOBB(box: Box3 | { min: Vector3Like; max: Vector3Like; } | OBB): box is OBB; /** * Snaps a quaternion to the nearest grid based on rotationSnapAngle. * This is useful for rotation snapping. * @param q The quaternion to snap. * @returns The snapped quaternion. */ protected snapQuaternionToGrid(q: Quaternion): Quaternion; /** * Undoes the last section box change */ protected undoSectionBox(): void; /** * Redoes the last undone section box change */ protected redoSectionBox(): void; /** * Cleanup method to remove event listeners and prevent memory leaks */ dispose(): void; }