123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785 |
- import { Observable } from "../Misc/observable.js";
- import { Logger } from "../Misc/logger.js";
- import { Quaternion, Matrix, Vector3 } from "../Maths/math.vector.js";
- import { AbstractMesh } from "../Meshes/abstractMesh.js";
- import { CreateSphere } from "../Meshes/Builders/sphereBuilder.js";
- import { CreateBox } from "../Meshes/Builders/boxBuilder.js";
- import { CreateLines } from "../Meshes/Builders/linesBuilder.js";
- import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior.js";
- import { Gizmo } from "./gizmo.js";
- import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer.js";
- import { StandardMaterial } from "../Materials/standardMaterial.js";
- import { PivotTools } from "../Misc/pivotTools.js";
- import { Color3 } from "../Maths/math.color.js";
- import { Epsilon } from "../Maths/math.constants.js";
- /**
- * Bounding box gizmo
- */
- export class BoundingBoxGizmo extends Gizmo {
- /**
- * Sets the axis factor
- * @param factor the Vector3 value
- */
- set axisFactor(factor) {
- this._axisFactor = factor;
- // update scale cube visibility
- const scaleBoxes = this._scaleBoxesParent.getChildMeshes();
- let index = 0;
- for (let i = 0; i < 3; i++) {
- for (let j = 0; j < 3; j++) {
- for (let k = 0; k < 3; k++) {
- const zeroAxisCount = (i === 1 ? 1 : 0) + (j === 1 ? 1 : 0) + (k === 1 ? 1 : 0);
- if (zeroAxisCount === 1 || zeroAxisCount === 3) {
- continue;
- }
- if (scaleBoxes[index]) {
- const dragAxis = new Vector3(i - 1, j - 1, k - 1);
- dragAxis.multiplyInPlace(this._axisFactor);
- scaleBoxes[index].setEnabled(dragAxis.lengthSquared() > Epsilon);
- }
- index++;
- }
- }
- }
- }
- /**
- * Gets the axis factor
- * @returns the Vector3 factor value
- */
- get axisFactor() {
- return this._axisFactor;
- }
- /**
- * Sets scale drag speed value
- * @param value the new speed value
- */
- set scaleDragSpeed(value) {
- this._scaleDragSpeed = value;
- }
- /**
- * Gets scale drag speed
- * @returns the scale speed number
- */
- get scaleDragSpeed() {
- return this._scaleDragSpeed;
- }
- /** Default material used to render when gizmo is not disabled or hovered */
- get coloredMaterial() {
- return this._coloredMaterial;
- }
- /** Material used to render when gizmo is hovered with mouse*/
- get hoverMaterial() {
- return this._hoverColoredMaterial;
- }
- /**
- * Get the pointerDragBehavior
- */
- get pointerDragBehavior() {
- return this._pointerDragBehavior;
- }
- /** True when a rotation sphere or scale box or a attached mesh is dragged */
- get isDragging() {
- return this._dragging || this._pointerDragBehavior.dragging;
- }
- /**
- * Sets the color of the bounding box gizmo
- * @param color the color to set
- */
- setColor(color) {
- this._coloredMaterial.emissiveColor = color;
- this._hoverColoredMaterial.emissiveColor = color.clone().add(new Color3(0.3, 0.3, 0.3));
- this._lineBoundingBox.getChildren().forEach((l) => {
- if (l.color) {
- l.color = color;
- }
- });
- }
- /**
- * Creates an BoundingBoxGizmo
- * @param color The color of the gizmo
- * @param gizmoLayer The utility layer the gizmo will be added to
- */
- constructor(color = Color3.Gray(), gizmoLayer = UtilityLayerRenderer.DefaultKeepDepthUtilityLayer) {
- super(gizmoLayer);
- this._boundingDimensions = new Vector3(1, 1, 1);
- this._renderObserver = null;
- this._pointerObserver = null;
- this._scaleDragSpeed = 0.2;
- this._rotateSpheresDragBehaviors = [];
- this._scaleBoxesDragBehaviors = [];
- /**
- * boolean updated when a rotation sphere or scale box is dragged
- */
- this._dragging = false;
- this._tmpQuaternion = new Quaternion();
- this._tmpVector = new Vector3(0, 0, 0);
- this._tmpRotationMatrix = new Matrix();
- this._incrementalStartupValue = Vector3.Zero();
- this._incrementalAnchorStartupValue = Vector3.Zero();
- /**
- * If child meshes should be ignored when calculating the bounding box. This should be set to true to avoid perf hits with heavily nested meshes (Default: false)
- */
- this.ignoreChildren = false;
- /**
- * Returns true if a descendant should be included when computing the bounding box. When null, all descendants are included. If ignoreChildren is set this will be ignored. (Default: null)
- */
- this.includeChildPredicate = null;
- /**
- * The size of the rotation spheres attached to the bounding box (Default: 0.1)
- */
- this.rotationSphereSize = 0.1;
- /**
- * The size of the scale boxes attached to the bounding box (Default: 0.1)
- */
- this.scaleBoxSize = 0.1;
- /**
- * If set, the rotation spheres and scale boxes will increase in size based on the distance away from the camera to have a consistent screen size (Default: false)
- * Note : fixedDragMeshScreenSize takes precedence over fixedDragMeshBoundsSize if both are true
- */
- this.fixedDragMeshScreenSize = false;
- /**
- * If set, the rotation spheres and scale boxes will increase in size based on the size of the bounding box
- * Note : fixedDragMeshScreenSize takes precedence over fixedDragMeshBoundsSize if both are true
- */
- this.fixedDragMeshBoundsSize = false;
- /**
- * The distance away from the object which the draggable meshes should appear world sized when fixedDragMeshScreenSize is set to true (default: 10)
- */
- this.fixedDragMeshScreenSizeDistanceFactor = 10;
- /**
- * Drag distance in babylon units that the gizmo will snap scaling to when dragged
- */
- this.scalingSnapDistance = 0;
- /**
- * Drag distance in babylon units that the gizmo will snap rotation to when dragged
- */
- this.rotationSnapDistance = 0;
- /**
- * Fired when a rotation sphere or scale box is dragged
- */
- this.onDragStartObservable = new Observable();
- /**
- * Fired when a scale box is dragged
- */
- this.onScaleBoxDragObservable = new Observable();
- /**
- * Fired when a scale box drag is ended
- */
- this.onScaleBoxDragEndObservable = new Observable();
- /**
- * Fired when a rotation sphere is dragged
- */
- this.onRotationSphereDragObservable = new Observable();
- /**
- * Fired when a rotation sphere drag is ended
- */
- this.onRotationSphereDragEndObservable = new Observable();
- /**
- * Relative bounding box pivot used when scaling the attached node. When null object with scale from the opposite corner. 0.5,0.5,0.5 for center and 0.5,0,0.5 for bottom (Default: null)
- */
- this.scalePivot = null;
- /**
- * Scale factor used for masking some axis
- */
- this._axisFactor = new Vector3(1, 1, 1);
- /**
- * Incremental snap scaling (default is false). When true, with a snapDistance of 0.1, scaling will be 1.1,1.2,1.3 instead of, when false: 1.1,1.21,1.33,...
- */
- this.incrementalSnap = false;
- this._existingMeshScale = new Vector3();
- // Dragging
- this._dragMesh = null;
- this._pointerDragBehavior = new PointerDragBehavior();
- // Do not update the gizmo's scale so it has a fixed size to the object its attached to
- this.updateScale = false;
- this._anchorMesh = new AbstractMesh("anchor", gizmoLayer.utilityLayerScene);
- // Create Materials
- this._coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
- this._coloredMaterial.disableLighting = true;
- this._hoverColoredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
- this._hoverColoredMaterial.disableLighting = true;
- // Build bounding box out of lines
- this._lineBoundingBox = new AbstractMesh("", gizmoLayer.utilityLayerScene);
- this._lineBoundingBox.rotationQuaternion = new Quaternion();
- const lines = [];
- lines.push(CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(this._boundingDimensions.x, 0, 0)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, 0, 0), new Vector3(0, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(this._boundingDimensions.x, 0, 0), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, this._boundingDimensions.y, 0), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", { points: [new Vector3(0, 0, this._boundingDimensions.z), new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z)] }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", {
- points: [
- new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z),
- new Vector3(0, this._boundingDimensions.y, this._boundingDimensions.z),
- ],
- }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", {
- points: [
- new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z),
- new Vector3(this._boundingDimensions.x, 0, this._boundingDimensions.z),
- ],
- }, gizmoLayer.utilityLayerScene));
- lines.push(CreateLines("lines", {
- points: [
- new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, this._boundingDimensions.z),
- new Vector3(this._boundingDimensions.x, this._boundingDimensions.y, 0),
- ],
- }, gizmoLayer.utilityLayerScene));
- lines.forEach((l) => {
- l.color = color;
- l.position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
- l.isPickable = false;
- this._lineBoundingBox.addChild(l);
- });
- this._rootMesh.addChild(this._lineBoundingBox);
- this.setColor(color);
- // Create rotation spheres
- this._rotateSpheresParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
- this._rotateSpheresParent.rotationQuaternion = new Quaternion();
- for (let i = 0; i < 12; i++) {
- const sphere = CreateSphere("", { diameter: 1 }, gizmoLayer.utilityLayerScene);
- sphere.rotationQuaternion = new Quaternion();
- sphere.material = this._coloredMaterial;
- sphere.isNearGrabbable = true;
- // Drag behavior
- const rotateSpheresDragBehavior = new PointerDragBehavior({});
- rotateSpheresDragBehavior.moveAttached = false;
- rotateSpheresDragBehavior.updateDragPlane = false;
- sphere.addBehavior(rotateSpheresDragBehavior);
- const startingTurnDirection = new Vector3(1, 0, 0);
- let totalTurnAmountOfDrag = 0;
- let previousProjectDist = 0;
- rotateSpheresDragBehavior.onDragStartObservable.add(() => {
- startingTurnDirection.copyFrom(sphere.forward);
- totalTurnAmountOfDrag = 0;
- previousProjectDist = 0;
- });
- rotateSpheresDragBehavior.onDragObservable.add((event) => {
- this.onRotationSphereDragObservable.notifyObservers({});
- if (this.attachedMesh) {
- const originalParent = this.attachedMesh.parent;
- if (originalParent && originalParent.scaling && originalParent.scaling.isNonUniformWithinEpsilon(0.001)) {
- Logger.Warn("BoundingBoxGizmo controls are not supported on child meshes with non-uniform parent scaling");
- return;
- }
- PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
- const worldDragDirection = startingTurnDirection;
- // Project the world right on to the drag plane
- const toSub = event.dragPlaneNormal.scale(Vector3.Dot(event.dragPlaneNormal, worldDragDirection));
- const dragAxis = worldDragDirection.subtract(toSub).normalizeToNew();
- // project drag delta on to the resulting drag axis and rotate based on that
- let projectDist = Vector3.Dot(dragAxis, event.delta) < 0 ? Math.abs(event.delta.length()) : -Math.abs(event.delta.length());
- // Make rotation relative to size of mesh.
- projectDist = (projectDist / this._boundingDimensions.length()) * this._anchorMesh.scaling.length();
- // Rotate based on axis
- if (!this.attachedMesh.rotationQuaternion) {
- this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y, this.attachedMesh.rotation.x, this.attachedMesh.rotation.z);
- }
- if (!this._anchorMesh.rotationQuaternion) {
- this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y, this._anchorMesh.rotation.x, this._anchorMesh.rotation.z);
- }
- // Do not allow the object to turn more than a full circle
- totalTurnAmountOfDrag += projectDist;
- if (Math.abs(totalTurnAmountOfDrag) <= 2 * Math.PI) {
- if (this.rotationSnapDistance > 0) {
- const dragSteps = Math.floor(Math.abs(totalTurnAmountOfDrag) / this.rotationSnapDistance) * (totalTurnAmountOfDrag < 0 ? -1 : 1);
- const angle = this.rotationSnapDistance * dragSteps;
- projectDist = angle - previousProjectDist;
- previousProjectDist = angle;
- }
- if (i >= 8) {
- Quaternion.RotationYawPitchRollToRef(0, 0, projectDist, this._tmpQuaternion);
- }
- else if (i >= 4) {
- Quaternion.RotationYawPitchRollToRef(projectDist, 0, 0, this._tmpQuaternion);
- }
- else {
- Quaternion.RotationYawPitchRollToRef(0, projectDist, 0, this._tmpQuaternion);
- }
- // if using pivot, move anchor so mesh will be at relative (0,0,0) when parented
- if (this.attachedMesh.isUsingPivotMatrix()) {
- this._anchorMesh.position.copyFrom(this.attachedMesh.position);
- }
- // Rotate around center of bounding box
- this._anchorMesh.addChild(this.attachedMesh);
- if (this._anchorMesh.getScene().useRightHandedSystem) {
- this._tmpQuaternion.conjugateInPlace();
- }
- this._tmpQuaternion.normalize();
- this._anchorMesh.rotationQuaternion.multiplyToRef(this._tmpQuaternion, this._anchorMesh.rotationQuaternion);
- this._anchorMesh.rotationQuaternion.normalize();
- this._anchorMesh.removeChild(this.attachedMesh);
- this.attachedMesh.setParent(originalParent);
- }
- this.updateBoundingBox();
- PivotTools._RestorePivotPoint(this.attachedMesh);
- }
- this._updateDummy();
- });
- // Selection/deselection
- rotateSpheresDragBehavior.onDragStartObservable.add(() => {
- this.onDragStartObservable.notifyObservers({});
- this._dragging = true;
- this._selectNode(sphere);
- });
- rotateSpheresDragBehavior.onDragEndObservable.add((event) => {
- this.onRotationSphereDragEndObservable.notifyObservers({});
- this._dragging = false;
- this._selectNode(null);
- this._updateDummy();
- this._unhoverMeshOnTouchUp(event.pointerInfo, sphere);
- });
- this._rotateSpheresDragBehaviors.push(rotateSpheresDragBehavior);
- this._rotateSpheresParent.addChild(sphere);
- }
- this._rootMesh.addChild(this._rotateSpheresParent);
- // Create scale cubes
- this._scaleBoxesParent = new AbstractMesh("", gizmoLayer.utilityLayerScene);
- this._scaleBoxesParent.rotationQuaternion = new Quaternion();
- for (let i = 0; i < 3; i++) {
- for (let j = 0; j < 3; j++) {
- for (let k = 0; k < 3; k++) {
- // create box for relevant axis
- const zeroAxisCount = (i === 1 ? 1 : 0) + (j === 1 ? 1 : 0) + (k === 1 ? 1 : 0);
- if (zeroAxisCount === 1 || zeroAxisCount === 3) {
- continue;
- }
- const box = CreateBox("", { size: 1 }, gizmoLayer.utilityLayerScene);
- box.material = this._coloredMaterial;
- box._internalMetadata = zeroAxisCount === 2; // None homogenous scale handle
- box.isNearGrabbable = true;
- // Dragging logic
- const dragAxis = new Vector3(i - 1, j - 1, k - 1).normalize();
- const scaleBoxesDragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
- scaleBoxesDragBehavior.updateDragPlane = false;
- scaleBoxesDragBehavior.moveAttached = false;
- let totalRelativeDragDistance = 0;
- let previousScale = 0;
- box.addBehavior(scaleBoxesDragBehavior);
- scaleBoxesDragBehavior.onDragObservable.add((event) => {
- this.onScaleBoxDragObservable.notifyObservers({});
- if (this.attachedMesh) {
- const originalParent = this.attachedMesh.parent;
- if (originalParent && originalParent.scaling && originalParent.scaling.isNonUniformWithinEpsilon(0.001)) {
- Logger.Warn("BoundingBoxGizmo controls are not supported on child meshes with non-uniform parent scaling");
- return;
- }
- PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
- let relativeDragDistance = (event.dragDistance / this._boundingDimensions.length()) * this._anchorMesh.scaling.length();
- totalRelativeDragDistance += relativeDragDistance;
- if (this.scalingSnapDistance > 0) {
- const dragSteps = Math.floor(Math.abs(totalRelativeDragDistance) / this.scalingSnapDistance) * (totalRelativeDragDistance < 0 ? -1 : 1);
- const scale = this.scalingSnapDistance * dragSteps;
- relativeDragDistance = scale - previousScale;
- previousScale = scale;
- }
- const deltaScale = new Vector3(relativeDragDistance, relativeDragDistance, relativeDragDistance);
- const fullScale = new Vector3(previousScale, previousScale, previousScale);
- if (zeroAxisCount === 2) {
- // scale on 1 axis when using the anchor box in the face middle
- deltaScale.x *= Math.abs(dragAxis.x);
- deltaScale.y *= Math.abs(dragAxis.y);
- deltaScale.z *= Math.abs(dragAxis.z);
- }
- deltaScale.scaleInPlace(this._scaleDragSpeed);
- deltaScale.multiplyInPlace(this._axisFactor);
- fullScale.scaleInPlace(this._scaleDragSpeed);
- fullScale.multiplyInPlace(this._axisFactor);
- fullScale.addInPlace(this._incrementalStartupValue);
- this.updateBoundingBox();
- if (this.scalePivot) {
- this.attachedMesh.getWorldMatrix().getRotationMatrixToRef(this._tmpRotationMatrix);
- // Move anchor to desired pivot point (Bottom left corner + dimension/2)
- this._boundingDimensions.scaleToRef(0.5, this._tmpVector);
- Vector3.TransformCoordinatesToRef(this._tmpVector, this._tmpRotationMatrix, this._tmpVector);
- this._anchorMesh.position.subtractInPlace(this._tmpVector);
- this._boundingDimensions.multiplyToRef(this.scalePivot, this._tmpVector);
- Vector3.TransformCoordinatesToRef(this._tmpVector, this._tmpRotationMatrix, this._tmpVector);
- this._anchorMesh.position.addInPlace(this._tmpVector);
- }
- else {
- // Scale from the position of the opposite corner
- box.absolutePosition.subtractToRef(this._anchorMesh.position, this._tmpVector);
- this._anchorMesh.position.subtractInPlace(this._tmpVector);
- if (this.attachedMesh.isUsingPivotMatrix()) {
- this._anchorMesh.position.subtractInPlace(this.attachedMesh.getPivotPoint());
- }
- }
- this._anchorMesh.addChild(this.attachedMesh);
- if (this.incrementalSnap) {
- fullScale.x /= Math.abs(this._incrementalStartupValue.x) < Epsilon ? 1 : this._incrementalStartupValue.x;
- fullScale.y /= Math.abs(this._incrementalStartupValue.y) < Epsilon ? 1 : this._incrementalStartupValue.y;
- fullScale.z /= Math.abs(this._incrementalStartupValue.z) < Epsilon ? 1 : this._incrementalStartupValue.z;
- fullScale.x = Math.max(this._incrementalAnchorStartupValue.x * fullScale.x, this.scalingSnapDistance);
- fullScale.y = Math.max(this._incrementalAnchorStartupValue.y * fullScale.y, this.scalingSnapDistance);
- fullScale.z = Math.max(this._incrementalAnchorStartupValue.z * fullScale.z, this.scalingSnapDistance);
- this._anchorMesh.scaling.x += (fullScale.x - this._anchorMesh.scaling.x) * Math.abs(dragAxis.x);
- this._anchorMesh.scaling.y += (fullScale.y - this._anchorMesh.scaling.y) * Math.abs(dragAxis.y);
- this._anchorMesh.scaling.z += (fullScale.z - this._anchorMesh.scaling.z) * Math.abs(dragAxis.z);
- }
- else {
- this._anchorMesh.scaling.addInPlace(deltaScale);
- if (this._anchorMesh.scaling.x < 0 || this._anchorMesh.scaling.y < 0 || this._anchorMesh.scaling.z < 0) {
- this._anchorMesh.scaling.subtractInPlace(deltaScale);
- }
- }
- this._anchorMesh.removeChild(this.attachedMesh);
- this.attachedMesh.setParent(originalParent);
- PivotTools._RestorePivotPoint(this.attachedMesh);
- }
- this._updateDummy();
- });
- // Selection/deselection
- scaleBoxesDragBehavior.onDragStartObservable.add(() => {
- this.onDragStartObservable.notifyObservers({});
- this._dragging = true;
- this._selectNode(box);
- totalRelativeDragDistance = 0;
- previousScale = 0;
- this._incrementalStartupValue.copyFrom(this.attachedMesh.scaling);
- this._incrementalAnchorStartupValue.copyFrom(this._anchorMesh.scaling);
- });
- scaleBoxesDragBehavior.onDragEndObservable.add((event) => {
- this.onScaleBoxDragEndObservable.notifyObservers({});
- this._dragging = false;
- this._selectNode(null);
- this._updateDummy();
- this._unhoverMeshOnTouchUp(event.pointerInfo, box);
- });
- this._scaleBoxesParent.addChild(box);
- this._scaleBoxesDragBehaviors.push(scaleBoxesDragBehavior);
- }
- }
- }
- this._rootMesh.addChild(this._scaleBoxesParent);
- // Hover color change
- const pointerIds = [];
- this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
- if (!pointerIds[pointerInfo.event.pointerId]) {
- this._rotateSpheresParent
- .getChildMeshes()
- .concat(this._scaleBoxesParent.getChildMeshes())
- .forEach((mesh) => {
- if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh == mesh) {
- pointerIds[pointerInfo.event.pointerId] = mesh;
- mesh.material = this._hoverColoredMaterial;
- this._isHovered = true;
- }
- });
- }
- else {
- if (pointerInfo.pickInfo && pointerInfo.pickInfo.pickedMesh != pointerIds[pointerInfo.event.pointerId]) {
- pointerIds[pointerInfo.event.pointerId].material = this._coloredMaterial;
- delete pointerIds[pointerInfo.event.pointerId];
- this._isHovered = false;
- }
- }
- });
- // Update bounding box positions
- this._renderObserver = this.gizmoLayer.originalScene.onBeforeRenderObservable.add(() => {
- // Only update the bounding box if scaling has changed
- if (this.attachedMesh && !this._existingMeshScale.equals(this.attachedMesh.scaling)) {
- this.updateBoundingBox();
- }
- else if (this.fixedDragMeshScreenSize || this.fixedDragMeshBoundsSize) {
- this._updateRotationSpheres();
- this._updateScaleBoxes();
- }
- // If drag mesh is enabled and dragging, update the attached mesh pose to match the drag mesh
- if (this._dragMesh && this.attachedMesh && this._pointerDragBehavior.dragging) {
- this._lineBoundingBox.position.rotateByQuaternionToRef(this._rootMesh.rotationQuaternion, this._tmpVector);
- this.attachedMesh.setAbsolutePosition(this._dragMesh.position.add(this._tmpVector.scale(-1)));
- }
- });
- this.updateBoundingBox();
- }
- _attachedNodeChanged(value) {
- if (value) {
- // Reset anchor mesh to match attached mesh's scale
- // This is needed to avoid invalid box/sphere position on first drag
- this._anchorMesh.scaling.setAll(1);
- PivotTools._RemoveAndStorePivotPoint(value);
- const originalParent = value.parent;
- this._anchorMesh.addChild(value);
- this._anchorMesh.removeChild(value);
- value.setParent(originalParent);
- PivotTools._RestorePivotPoint(value);
- this.updateBoundingBox();
- value.getChildMeshes(false).forEach((m) => {
- m.markAsDirty("scaling");
- });
- this.gizmoLayer.utilityLayerScene.onAfterRenderObservable.addOnce(() => {
- this._updateDummy();
- });
- }
- }
- _selectNode(selectedMesh) {
- this._rotateSpheresParent
- .getChildMeshes()
- .concat(this._scaleBoxesParent.getChildMeshes())
- .forEach((m) => {
- m.isVisible = !selectedMesh || m == selectedMesh;
- });
- }
- _unhoverMeshOnTouchUp(pointerInfo, selectedMesh) {
- // force unhover mesh if not a mouse event
- if (pointerInfo?.event instanceof PointerEvent && pointerInfo?.event.pointerType === "touch") {
- selectedMesh.material = this._coloredMaterial;
- }
- }
- /**
- * returns an array containing all boxes used for scaling (in increasing x, y and z orders)
- * @returns array of scaling boxes
- */
- getScaleBoxes() {
- return this._scaleBoxesParent.getChildMeshes();
- }
- /**
- * Updates the bounding box information for the Gizmo
- */
- updateBoundingBox() {
- if (this.attachedMesh) {
- PivotTools._RemoveAndStorePivotPoint(this.attachedMesh);
- // Store original parent
- const originalParent = this.attachedMesh.parent;
- this.attachedMesh.setParent(null);
- this._update();
- // Rotate based on axis
- if (!this.attachedMesh.rotationQuaternion) {
- this.attachedMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this.attachedMesh.rotation.y, this.attachedMesh.rotation.x, this.attachedMesh.rotation.z);
- }
- if (!this._anchorMesh.rotationQuaternion) {
- this._anchorMesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(this._anchorMesh.rotation.y, this._anchorMesh.rotation.x, this._anchorMesh.rotation.z);
- }
- this._anchorMesh.rotationQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
- // Store original position and reset mesh to origin before computing the bounding box
- this._tmpQuaternion.copyFrom(this.attachedMesh.rotationQuaternion);
- this._tmpVector.copyFrom(this.attachedMesh.position);
- this.attachedMesh.rotationQuaternion.set(0, 0, 0, 1);
- this.attachedMesh.position.set(0, 0, 0);
- // Update bounding dimensions/positions
- const boundingMinMax = this.attachedMesh.getHierarchyBoundingVectors(!this.ignoreChildren, this.includeChildPredicate);
- boundingMinMax.max.subtractToRef(boundingMinMax.min, this._boundingDimensions);
- // Update gizmo to match bounding box scaling and rotation
- // The position set here is the offset from the origin for the boundingbox when the attached mesh is at the origin
- // The position of the gizmo is then set to the attachedMesh in gizmo._update
- this._lineBoundingBox.scaling.copyFrom(this._boundingDimensions);
- this._lineBoundingBox.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
- this._rotateSpheresParent.position.copyFrom(this._lineBoundingBox.position);
- this._scaleBoxesParent.position.copyFrom(this._lineBoundingBox.position);
- this._lineBoundingBox.computeWorldMatrix();
- this._anchorMesh.position.copyFrom(this._lineBoundingBox.absolutePosition);
- // Restore position/rotation values
- this.attachedMesh.rotationQuaternion.copyFrom(this._tmpQuaternion);
- this.attachedMesh.position.copyFrom(this._tmpVector);
- // Restore original parent
- this.attachedMesh.setParent(originalParent);
- }
- this._updateRotationSpheres();
- this._updateScaleBoxes();
- if (this.attachedMesh) {
- this._existingMeshScale.copyFrom(this.attachedMesh.scaling);
- PivotTools._RestorePivotPoint(this.attachedMesh);
- }
- }
- _updateRotationSpheres() {
- const rotateSpheres = this._rotateSpheresParent.getChildMeshes();
- for (let i = 0; i < 3; i++) {
- for (let j = 0; j < 2; j++) {
- for (let k = 0; k < 2; k++) {
- const index = i * 4 + j * 2 + k;
- if (i == 0) {
- rotateSpheres[index].position.set(this._boundingDimensions.x / 2, this._boundingDimensions.y * j, this._boundingDimensions.z * k);
- rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
- rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Right()).normalizeToNew().add(rotateSpheres[index].position));
- }
- if (i == 1) {
- rotateSpheres[index].position.set(this._boundingDimensions.x * j, this._boundingDimensions.y / 2, this._boundingDimensions.z * k);
- rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
- rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Up()).normalizeToNew().add(rotateSpheres[index].position));
- }
- if (i == 2) {
- rotateSpheres[index].position.set(this._boundingDimensions.x * j, this._boundingDimensions.y * k, this._boundingDimensions.z / 2);
- rotateSpheres[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
- rotateSpheres[index].lookAt(Vector3.Cross(rotateSpheres[index].position.normalizeToNew(), Vector3.Forward()).normalizeToNew().add(rotateSpheres[index].position));
- }
- if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
- rotateSpheres[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.position, this._tmpVector);
- const distanceFromCamera = (this.rotationSphereSize * this._tmpVector.length()) / this.fixedDragMeshScreenSizeDistanceFactor;
- rotateSpheres[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
- }
- else if (this.fixedDragMeshBoundsSize) {
- rotateSpheres[index].scaling.set(this.rotationSphereSize * this._boundingDimensions.x, this.rotationSphereSize * this._boundingDimensions.y, this.rotationSphereSize * this._boundingDimensions.z);
- }
- else {
- rotateSpheres[index].scaling.set(this.rotationSphereSize, this.rotationSphereSize, this.rotationSphereSize);
- }
- }
- }
- }
- }
- _updateScaleBoxes() {
- const scaleBoxes = this._scaleBoxesParent.getChildMeshes();
- let index = 0;
- for (let i = 0; i < 3; i++) {
- for (let j = 0; j < 3; j++) {
- for (let k = 0; k < 3; k++) {
- const zeroAxisCount = (i === 1 ? 1 : 0) + (j === 1 ? 1 : 0) + (k === 1 ? 1 : 0);
- if (zeroAxisCount === 1 || zeroAxisCount === 3) {
- continue;
- }
- if (scaleBoxes[index]) {
- scaleBoxes[index].position.set(this._boundingDimensions.x * (i / 2), this._boundingDimensions.y * (j / 2), this._boundingDimensions.z * (k / 2));
- scaleBoxes[index].position.addInPlace(new Vector3(-this._boundingDimensions.x / 2, -this._boundingDimensions.y / 2, -this._boundingDimensions.z / 2));
- if (this.fixedDragMeshScreenSize && this.gizmoLayer.utilityLayerScene.activeCamera) {
- scaleBoxes[index].absolutePosition.subtractToRef(this.gizmoLayer.utilityLayerScene.activeCamera.globalPosition, this._tmpVector);
- const distanceFromCamera = (this.scaleBoxSize * this._tmpVector.length()) / this.fixedDragMeshScreenSizeDistanceFactor;
- scaleBoxes[index].scaling.set(distanceFromCamera, distanceFromCamera, distanceFromCamera);
- }
- else if (this.fixedDragMeshBoundsSize) {
- scaleBoxes[index].scaling.set(this.scaleBoxSize * this._boundingDimensions.x, this.scaleBoxSize * this._boundingDimensions.y, this.scaleBoxSize * this._boundingDimensions.z);
- }
- else {
- scaleBoxes[index].scaling.set(this.scaleBoxSize, this.scaleBoxSize, this.scaleBoxSize);
- }
- }
- index++;
- }
- }
- }
- }
- /**
- * Enables rotation on the specified axis and disables rotation on the others
- * @param axis The list of axis that should be enabled (eg. "xy" or "xyz")
- */
- setEnabledRotationAxis(axis) {
- this._rotateSpheresParent.getChildMeshes().forEach((m, i) => {
- if (i < 4) {
- m.setEnabled(axis.indexOf("x") != -1);
- }
- else if (i < 8) {
- m.setEnabled(axis.indexOf("y") != -1);
- }
- else {
- m.setEnabled(axis.indexOf("z") != -1);
- }
- });
- }
- /**
- * Enables/disables scaling
- * @param enable if scaling should be enabled
- * @param homogeneousScaling defines if scaling should only be homogeneous
- */
- setEnabledScaling(enable, homogeneousScaling = false) {
- this._scaleBoxesParent.getChildMeshes().forEach((m) => {
- let enableMesh = enable;
- // Disable heterogeneous scale handles if requested.
- if (homogeneousScaling && m._internalMetadata === true) {
- enableMesh = false;
- }
- m.setEnabled(enableMesh);
- });
- }
- _updateDummy() {
- if (this._dragMesh) {
- this._dragMesh.position.copyFrom(this._lineBoundingBox.getAbsolutePosition());
- this._dragMesh.scaling.copyFrom(this._lineBoundingBox.scaling);
- this._dragMesh.rotationQuaternion.copyFrom(this._rootMesh.rotationQuaternion);
- }
- }
- /**
- * Enables a pointer drag behavior on the bounding box of the gizmo
- */
- enableDragBehavior() {
- this._dragMesh = CreateBox("dummy", { size: 1 }, this.gizmoLayer.utilityLayerScene);
- this._dragMesh.visibility = 0;
- this._dragMesh.rotationQuaternion = new Quaternion();
- this._pointerDragBehavior.useObjectOrientationForDragging = false;
- this._dragMesh.addBehavior(this._pointerDragBehavior);
- }
- /**
- * Force release the drag action by code
- */
- releaseDrag() {
- this._scaleBoxesDragBehaviors.forEach((dragBehavior) => {
- dragBehavior.releaseDrag();
- });
- this._rotateSpheresDragBehaviors.forEach((dragBehavior) => {
- dragBehavior.releaseDrag();
- });
- this._pointerDragBehavior.releaseDrag();
- }
- /**
- * Disposes of the gizmo
- */
- dispose() {
- this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
- this.gizmoLayer.originalScene.onBeforeRenderObservable.remove(this._renderObserver);
- this._lineBoundingBox.dispose();
- this._rotateSpheresParent.dispose();
- this._scaleBoxesParent.dispose();
- if (this._dragMesh) {
- this._dragMesh.dispose();
- }
- this._scaleBoxesDragBehaviors.length = 0;
- this._rotateSpheresDragBehaviors.length = 0;
- super.dispose();
- }
- /**
- * Makes a mesh not pickable and wraps the mesh inside of a bounding box mesh that is pickable. (This is useful to avoid picking within complex geometry)
- * @param mesh the mesh to wrap in the bounding box mesh and make not pickable
- * @returns the bounding box mesh with the passed in mesh as a child
- */
- static MakeNotPickableAndWrapInBoundingBox(mesh) {
- const makeNotPickable = (root) => {
- root.isPickable = false;
- root.getChildMeshes().forEach((c) => {
- makeNotPickable(c);
- });
- };
- makeNotPickable(mesh);
- // Reset position to get bounding box from origin with no rotation
- if (!mesh.rotationQuaternion) {
- mesh.rotationQuaternion = Quaternion.RotationYawPitchRoll(mesh.rotation.y, mesh.rotation.x, mesh.rotation.z);
- }
- const oldPos = mesh.position.clone();
- const oldRot = mesh.rotationQuaternion.clone();
- mesh.rotationQuaternion.set(0, 0, 0, 1);
- mesh.position.set(0, 0, 0);
- // Update bounding dimensions/positions
- const box = CreateBox("box", { size: 1 }, mesh.getScene());
- const boundingMinMax = mesh.getHierarchyBoundingVectors();
- boundingMinMax.max.subtractToRef(boundingMinMax.min, box.scaling);
- // Adjust scale to avoid undefined behavior when adding child
- if (box.scaling.y === 0) {
- box.scaling.y = Epsilon;
- }
- if (box.scaling.x === 0) {
- box.scaling.x = Epsilon;
- }
- if (box.scaling.z === 0) {
- box.scaling.z = Epsilon;
- }
- box.position.set((boundingMinMax.max.x + boundingMinMax.min.x) / 2, (boundingMinMax.max.y + boundingMinMax.min.y) / 2, (boundingMinMax.max.z + boundingMinMax.min.z) / 2);
- // Restore original positions
- mesh.addChild(box);
- mesh.rotationQuaternion.copyFrom(oldRot);
- mesh.position.copyFrom(oldPos);
- // Reverse parenting
- mesh.removeChild(box);
- box.addChild(mesh);
- box.visibility = 0;
- return box;
- }
- /**
- * CustomMeshes are not supported by this gizmo
- */
- setCustomMesh() {
- Logger.Error("Custom meshes are not supported on this gizmo");
- }
- }
- //# sourceMappingURL=boundingBoxGizmo.js.map
|