123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523 |
- import { Quaternion, Vector3, Matrix, TmpVectors } from "../Maths/math.vector.js";
- import { Mesh } from "../Meshes/mesh.js";
- import { Camera } from "../Cameras/camera.js";
- import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer.js";
- import { PointerEventTypes } from "../Events/pointerEvents.js";
- import { Light } from "../Lights/light.js";
- /**
- * Anchor options where the Gizmo can be positioned in relation to its anchored node
- */
- export var GizmoAnchorPoint;
- (function (GizmoAnchorPoint) {
- /** The origin of the attached node */
- GizmoAnchorPoint[GizmoAnchorPoint["Origin"] = 0] = "Origin";
- /** The pivot point of the attached node*/
- GizmoAnchorPoint[GizmoAnchorPoint["Pivot"] = 1] = "Pivot";
- })(GizmoAnchorPoint || (GizmoAnchorPoint = {}));
- /**
- * Coordinates mode: Local or World. Defines how axis is aligned: either on world axis or transform local axis
- */
- export var GizmoCoordinatesMode;
- (function (GizmoCoordinatesMode) {
- GizmoCoordinatesMode[GizmoCoordinatesMode["World"] = 0] = "World";
- GizmoCoordinatesMode[GizmoCoordinatesMode["Local"] = 1] = "Local";
- })(GizmoCoordinatesMode || (GizmoCoordinatesMode = {}));
- /**
- * Renders gizmos on top of an existing scene which provide controls for position, rotation, etc.
- */
- export class Gizmo {
- /**
- * Ratio for the scale of the gizmo (Default: 1)
- */
- set scaleRatio(value) {
- this._scaleRatio = value;
- }
- get scaleRatio() {
- return this._scaleRatio;
- }
- /**
- * True when the mouse pointer is hovered a gizmo mesh
- */
- get isHovered() {
- return this._isHovered;
- }
- /**
- * Mesh that the gizmo will be attached to. (eg. on a drag gizmo the mesh that will be dragged)
- * * When set, interactions will be enabled
- */
- get attachedMesh() {
- return this._attachedMesh;
- }
- set attachedMesh(value) {
- this._attachedMesh = value;
- if (value) {
- this._attachedNode = value;
- }
- this._rootMesh.setEnabled(value ? true : false);
- this._attachedNodeChanged(value);
- }
- /**
- * Node that the gizmo will be attached to. (eg. on a drag gizmo the mesh, bone or NodeTransform that will be dragged)
- * * When set, interactions will be enabled
- */
- get attachedNode() {
- return this._attachedNode;
- }
- set attachedNode(value) {
- this._attachedNode = value;
- this._attachedMesh = null;
- this._rootMesh.setEnabled(value ? true : false);
- this._attachedNodeChanged(value);
- }
- /**
- * Disposes and replaces the current meshes in the gizmo with the specified mesh
- * @param mesh The mesh to replace the default mesh of the gizmo
- */
- setCustomMesh(mesh) {
- if (mesh.getScene() != this.gizmoLayer.utilityLayerScene) {
- // eslint-disable-next-line no-throw-literal
- throw "When setting a custom mesh on a gizmo, the custom meshes scene must be the same as the gizmos (eg. gizmo.gizmoLayer.utilityLayerScene)";
- }
- this._rootMesh.getChildMeshes().forEach((c) => {
- c.dispose();
- });
- mesh.parent = this._rootMesh;
- this._customMeshSet = true;
- }
- /**
- * Additional transform applied to the gizmo.
- * It's useful when the gizmo is attached to a bone: if the bone is part of a skeleton attached to a mesh, you should define the mesh as additionalTransformNode if you want the gizmo to be displayed at the bone's correct location.
- * Otherwise, as the gizmo is relative to the skeleton root, the mesh transformation will not be taken into account.
- */
- get additionalTransformNode() {
- return this._additionalTransformNode;
- }
- set additionalTransformNode(value) {
- this._additionalTransformNode = value;
- }
- /**
- * If set the gizmo's rotation will be updated to match the attached mesh each frame (Default: true)
- * NOTE: This is only possible for meshes with uniform scaling, as otherwise it's not possible to decompose the rotation
- */
- set updateGizmoRotationToMatchAttachedMesh(value) {
- this._updateGizmoRotationToMatchAttachedMesh = value;
- }
- get updateGizmoRotationToMatchAttachedMesh() {
- return this._updateGizmoRotationToMatchAttachedMesh;
- }
- /**
- * If set the gizmo's position will be updated to match the attached mesh each frame (Default: true)
- */
- set updateGizmoPositionToMatchAttachedMesh(value) {
- this._updateGizmoPositionToMatchAttachedMesh = value;
- }
- get updateGizmoPositionToMatchAttachedMesh() {
- return this._updateGizmoPositionToMatchAttachedMesh;
- }
- /**
- * Defines where the gizmo will be positioned if `updateGizmoPositionToMatchAttachedMesh` is enabled.
- * (Default: GizmoAnchorPoint.Origin)
- */
- set anchorPoint(value) {
- this._anchorPoint = value;
- }
- get anchorPoint() {
- return this._anchorPoint;
- }
- /**
- * Set the coordinate system to use. By default it's local.
- * But it's possible for a user to tweak so its local for translation and world for rotation.
- * In that case, setting the coordinate system will change `updateGizmoRotationToMatchAttachedMesh` and `updateGizmoPositionToMatchAttachedMesh`
- */
- set coordinatesMode(coordinatesMode) {
- this._coordinatesMode = coordinatesMode;
- const local = coordinatesMode == GizmoCoordinatesMode.Local;
- this.updateGizmoRotationToMatchAttachedMesh = local;
- this.updateGizmoPositionToMatchAttachedMesh = true;
- }
- get coordinatesMode() {
- return this._coordinatesMode;
- }
- /**
- * When set, the gizmo will always appear the same size no matter where the camera is (default: true)
- */
- set updateScale(value) {
- this._updateScale = value;
- }
- get updateScale() {
- return this._updateScale;
- }
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- _attachedNodeChanged(value) { }
- /**
- * Creates a gizmo
- * @param gizmoLayer The utility layer the gizmo will be added to
- */
- constructor(
- /** The utility layer the gizmo will be added to */
- gizmoLayer = UtilityLayerRenderer.DefaultUtilityLayer) {
- this.gizmoLayer = gizmoLayer;
- this._attachedMesh = null;
- this._attachedNode = null;
- this._customRotationQuaternion = null;
- /**
- * Ratio for the scale of the gizmo (Default: 1)
- */
- this._scaleRatio = 1;
- /**
- * boolean updated by pointermove when a gizmo mesh is hovered
- */
- this._isHovered = false;
- /**
- * If a custom mesh has been set (Default: false)
- */
- this._customMeshSet = false;
- this._updateGizmoRotationToMatchAttachedMesh = true;
- this._updateGizmoPositionToMatchAttachedMesh = true;
- this._anchorPoint = GizmoAnchorPoint.Origin;
- this._updateScale = true;
- this._coordinatesMode = GizmoCoordinatesMode.Local;
- this._interactionsEnabled = true;
- this._rightHandtoLeftHandMatrix = Matrix.RotationY(Math.PI);
- this._rootMesh = new Mesh("gizmoRootNode", gizmoLayer.utilityLayerScene);
- this._rootMesh.rotationQuaternion = Quaternion.Identity();
- this._beforeRenderObserver = this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.add(() => {
- this._update();
- });
- }
- /**
- * posture that the gizmo will be display
- * When set null, default value will be used (Quaternion(0, 0, 0, 1))
- */
- get customRotationQuaternion() {
- return this._customRotationQuaternion;
- }
- set customRotationQuaternion(customRotationQuaternion) {
- this._customRotationQuaternion = customRotationQuaternion;
- }
- /**
- * Updates the gizmo to match the attached mesh's position/rotation
- */
- _update() {
- if (this.attachedNode) {
- let effectiveNode = this.attachedNode;
- if (this.attachedMesh) {
- effectiveNode = this.attachedMesh || this.attachedNode;
- }
- // Position
- if (this.updateGizmoPositionToMatchAttachedMesh) {
- if (this.anchorPoint == GizmoAnchorPoint.Pivot && effectiveNode.getAbsolutePivotPoint) {
- const position = effectiveNode.getAbsolutePivotPoint();
- this._rootMesh.position.copyFrom(position);
- }
- else {
- const row = effectiveNode.getWorldMatrix().getRow(3);
- const position = row ? row.toVector3() : new Vector3(0, 0, 0);
- this._rootMesh.position.copyFrom(position);
- }
- }
- // Rotation
- if (this.updateGizmoRotationToMatchAttachedMesh) {
- const supportedNode = effectiveNode._isMesh ||
- effectiveNode.getClassName() === "AbstractMesh" ||
- effectiveNode.getClassName() === "TransformNode" ||
- effectiveNode.getClassName() === "InstancedMesh";
- const transformNode = supportedNode ? effectiveNode : undefined;
- effectiveNode.getWorldMatrix().decompose(undefined, this._rootMesh.rotationQuaternion, undefined, Gizmo.PreserveScaling ? transformNode : undefined);
- this._rootMesh.rotationQuaternion.normalize();
- }
- else {
- if (this._customRotationQuaternion) {
- this._rootMesh.rotationQuaternion.copyFrom(this._customRotationQuaternion);
- }
- else {
- this._rootMesh.rotationQuaternion.set(0, 0, 0, 1);
- }
- }
- // Scale
- if (this.updateScale) {
- const activeCamera = this.gizmoLayer.utilityLayerScene.activeCamera;
- const cameraPosition = activeCamera.globalPosition;
- this._rootMesh.position.subtractToRef(cameraPosition, TmpVectors.Vector3[0]);
- let scale = this.scaleRatio;
- if (activeCamera.mode == Camera.ORTHOGRAPHIC_CAMERA) {
- if (activeCamera.orthoTop && activeCamera.orthoBottom) {
- const orthoHeight = activeCamera.orthoTop - activeCamera.orthoBottom;
- scale *= orthoHeight;
- }
- }
- else {
- const camForward = activeCamera.getScene().useRightHandedSystem ? Vector3.RightHandedForwardReadOnly : Vector3.LeftHandedForwardReadOnly;
- const direction = activeCamera.getDirection(camForward);
- scale *= Vector3.Dot(TmpVectors.Vector3[0], direction);
- }
- this._rootMesh.scaling.setAll(scale);
- // Account for handedness, similar to Matrix.decompose
- if (effectiveNode._getWorldMatrixDeterminant() < 0 && !Gizmo.PreserveScaling) {
- this._rootMesh.scaling.y *= -1;
- }
- }
- else {
- this._rootMesh.scaling.setAll(this.scaleRatio);
- }
- }
- if (this.additionalTransformNode) {
- this._rootMesh.computeWorldMatrix(true);
- this._rootMesh.getWorldMatrix().multiplyToRef(this.additionalTransformNode.getWorldMatrix(), TmpVectors.Matrix[0]);
- TmpVectors.Matrix[0].decompose(this._rootMesh.scaling, this._rootMesh.rotationQuaternion, this._rootMesh.position);
- }
- }
- /**
- * if transform has a pivot and is not using PostMultiplyPivotMatrix, then the worldMatrix contains the pivot matrix (it's not cancelled at the end)
- * so, when extracting the world matrix component, the translation (and other components) is containing the pivot translation.
- * And the pivot is applied each frame. Removing it anyway here makes it applied only in computeWorldMatrix.
- * @param transform local transform that needs to be transform by the pivot inverse matrix
- * @param localMatrix local matrix that needs to be transform by the pivot inverse matrix
- * @param result resulting matrix transformed by pivot inverse if the transform node is using pivot without using post Multiply Pivot Matrix
- */
- _handlePivotMatrixInverse(transform, localMatrix, result) {
- if (transform.isUsingPivotMatrix() && !transform.isUsingPostMultiplyPivotMatrix()) {
- transform.getPivotMatrix().invertToRef(TmpVectors.Matrix[5]);
- TmpVectors.Matrix[5].multiplyToRef(localMatrix, result);
- return;
- }
- result.copyFrom(localMatrix);
- }
- /**
- * computes the rotation/scaling/position of the transform once the Node world matrix has changed.
- */
- _matrixChanged() {
- if (!this._attachedNode) {
- return;
- }
- if (this._attachedNode._isCamera) {
- const camera = this._attachedNode;
- let worldMatrix;
- let worldMatrixUC;
- if (camera.parent) {
- const parentInv = TmpVectors.Matrix[1];
- camera.parent._worldMatrix.invertToRef(parentInv);
- this._attachedNode._worldMatrix.multiplyToRef(parentInv, TmpVectors.Matrix[0]);
- worldMatrix = TmpVectors.Matrix[0];
- }
- else {
- worldMatrix = this._attachedNode._worldMatrix;
- }
- if (camera.getScene().useRightHandedSystem) {
- // avoid desync with RH matrix computation. Otherwise, rotation of PI around Y axis happens each frame resulting in axis flipped because worldMatrix is computed as inverse of viewMatrix.
- this._rightHandtoLeftHandMatrix.multiplyToRef(worldMatrix, TmpVectors.Matrix[1]);
- worldMatrixUC = TmpVectors.Matrix[1];
- }
- else {
- worldMatrixUC = worldMatrix;
- }
- worldMatrixUC.decompose(TmpVectors.Vector3[1], TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
- const inheritsTargetCamera = this._attachedNode.getClassName() === "FreeCamera" ||
- this._attachedNode.getClassName() === "FlyCamera" ||
- this._attachedNode.getClassName() === "ArcFollowCamera" ||
- this._attachedNode.getClassName() === "TargetCamera" ||
- this._attachedNode.getClassName() === "TouchCamera" ||
- this._attachedNode.getClassName() === "UniversalCamera";
- if (inheritsTargetCamera) {
- const targetCamera = this._attachedNode;
- targetCamera.rotation = TmpVectors.Quaternion[0].toEulerAngles();
- if (targetCamera.rotationQuaternion) {
- targetCamera.rotationQuaternion.copyFrom(TmpVectors.Quaternion[0]);
- targetCamera.rotationQuaternion.normalize();
- }
- }
- camera.position.copyFrom(TmpVectors.Vector3[0]);
- }
- else if (this._attachedNode._isMesh ||
- this._attachedNode.getClassName() === "AbstractMesh" ||
- this._attachedNode.getClassName() === "TransformNode" ||
- this._attachedNode.getClassName() === "InstancedMesh") {
- const transform = this._attachedNode;
- if (transform.parent) {
- const parentInv = TmpVectors.Matrix[0];
- const localMat = TmpVectors.Matrix[1];
- transform.parent.getWorldMatrix().invertToRef(parentInv);
- this._attachedNode.getWorldMatrix().multiplyToRef(parentInv, localMat);
- const matrixToDecompose = TmpVectors.Matrix[4];
- this._handlePivotMatrixInverse(transform, localMat, matrixToDecompose);
- matrixToDecompose.decompose(TmpVectors.Vector3[0], TmpVectors.Quaternion[0], transform.position, Gizmo.PreserveScaling ? transform : undefined, Gizmo.UseAbsoluteScaling);
- TmpVectors.Quaternion[0].normalize();
- if (transform.isUsingPivotMatrix()) {
- // Calculate the local matrix without the translation.
- // Copied from TranslateNode.computeWorldMatrix
- const r = TmpVectors.Quaternion[1];
- Quaternion.RotationYawPitchRollToRef(transform.rotation.y, transform.rotation.x, transform.rotation.z, r);
- const scaleMatrix = TmpVectors.Matrix[2];
- Matrix.ScalingToRef(transform.scaling.x, transform.scaling.y, transform.scaling.z, scaleMatrix);
- const rotationMatrix = TmpVectors.Matrix[2];
- r.toRotationMatrix(rotationMatrix);
- const pivotMatrix = transform.getPivotMatrix();
- const invPivotMatrix = TmpVectors.Matrix[3];
- pivotMatrix.invertToRef(invPivotMatrix);
- pivotMatrix.multiplyToRef(scaleMatrix, TmpVectors.Matrix[4]);
- TmpVectors.Matrix[4].multiplyToRef(rotationMatrix, TmpVectors.Matrix[5]);
- TmpVectors.Matrix[5].multiplyToRef(invPivotMatrix, TmpVectors.Matrix[6]);
- TmpVectors.Matrix[6].getTranslationToRef(TmpVectors.Vector3[1]);
- transform.position.subtractInPlace(TmpVectors.Vector3[1]);
- }
- }
- else {
- const matrixToDecompose = TmpVectors.Matrix[4];
- this._handlePivotMatrixInverse(transform, this._attachedNode._worldMatrix, matrixToDecompose);
- matrixToDecompose.decompose(TmpVectors.Vector3[0], TmpVectors.Quaternion[0], transform.position, Gizmo.PreserveScaling ? transform : undefined, Gizmo.UseAbsoluteScaling);
- }
- TmpVectors.Vector3[0].scaleInPlace(1.0 / transform.scalingDeterminant);
- transform.scaling.copyFrom(TmpVectors.Vector3[0]);
- if (!transform.billboardMode) {
- if (transform.rotationQuaternion) {
- transform.rotationQuaternion.copyFrom(TmpVectors.Quaternion[0]);
- transform.rotationQuaternion.normalize();
- }
- else {
- transform.rotation = TmpVectors.Quaternion[0].toEulerAngles();
- }
- }
- }
- else if (this._attachedNode.getClassName() === "Bone") {
- const bone = this._attachedNode;
- const parent = bone.getParent();
- if (parent) {
- const invParent = TmpVectors.Matrix[0];
- const boneLocalMatrix = TmpVectors.Matrix[1];
- parent.getFinalMatrix().invertToRef(invParent);
- bone.getFinalMatrix().multiplyToRef(invParent, boneLocalMatrix);
- const lmat = bone.getLocalMatrix();
- lmat.copyFrom(boneLocalMatrix);
- }
- else {
- const lmat = bone.getLocalMatrix();
- lmat.copyFrom(bone.getFinalMatrix());
- }
- bone.markAsDirty();
- }
- else {
- const light = this._attachedNode;
- if (light.getTypeID) {
- const type = light.getTypeID();
- if (type === Light.LIGHTTYPEID_DIRECTIONALLIGHT || type === Light.LIGHTTYPEID_SPOTLIGHT || type === Light.LIGHTTYPEID_POINTLIGHT) {
- const parent = light.parent;
- if (parent) {
- const invParent = TmpVectors.Matrix[0];
- const nodeLocalMatrix = TmpVectors.Matrix[1];
- parent.getWorldMatrix().invertToRef(invParent);
- light.getWorldMatrix().multiplyToRef(invParent, nodeLocalMatrix);
- nodeLocalMatrix.decompose(undefined, TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
- }
- else {
- this._attachedNode._worldMatrix.decompose(undefined, TmpVectors.Quaternion[0], TmpVectors.Vector3[0]);
- }
- // setter doesn't copy values. Need a new Vector3
- light.position = new Vector3(TmpVectors.Vector3[0].x, TmpVectors.Vector3[0].y, TmpVectors.Vector3[0].z);
- if (light.direction) {
- light.direction = new Vector3(light.direction.x, light.direction.y, light.direction.z);
- }
- }
- }
- }
- }
- /**
- * refresh gizmo mesh material
- * @param gizmoMeshes
- * @param material material to apply
- */
- _setGizmoMeshMaterial(gizmoMeshes, material) {
- if (gizmoMeshes) {
- gizmoMeshes.forEach((m) => {
- m.material = material;
- if (m.color) {
- m.color = material.diffuseColor;
- }
- });
- }
- }
- /**
- * Subscribes to pointer up, down, and hover events. Used for responsive gizmos.
- * @param gizmoLayer The utility layer the gizmo will be added to
- * @param gizmoAxisCache Gizmo axis definition used for reactive gizmo UI
- * @returns {Observer<PointerInfo>} pointerObserver
- */
- static GizmoAxisPointerObserver(gizmoLayer, gizmoAxisCache) {
- let dragging = false;
- const pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
- if (pointerInfo.pickInfo) {
- // On Hover Logic
- if (pointerInfo.type === PointerEventTypes.POINTERMOVE) {
- if (dragging) {
- return;
- }
- gizmoAxisCache.forEach((cache) => {
- if (cache.colliderMeshes && cache.gizmoMeshes) {
- const isHovered = cache.colliderMeshes?.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1;
- const material = cache.dragBehavior.enabled ? (isHovered || cache.active ? cache.hoverMaterial : cache.material) : cache.disableMaterial;
- cache.gizmoMeshes.forEach((m) => {
- m.material = material;
- if (m.color) {
- m.color = material.diffuseColor;
- }
- });
- }
- });
- }
- // On Mouse Down
- if (pointerInfo.type === PointerEventTypes.POINTERDOWN) {
- // If user Clicked Gizmo
- if (gizmoAxisCache.has(pointerInfo.pickInfo.pickedMesh?.parent)) {
- dragging = true;
- const statusMap = gizmoAxisCache.get(pointerInfo.pickInfo.pickedMesh?.parent);
- statusMap.active = true;
- gizmoAxisCache.forEach((cache) => {
- const isHovered = cache.colliderMeshes?.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1;
- const material = (isHovered || cache.active) && cache.dragBehavior.enabled ? cache.hoverMaterial : cache.disableMaterial;
- cache.gizmoMeshes.forEach((m) => {
- m.material = material;
- if (m.color) {
- m.color = material.diffuseColor;
- }
- });
- });
- }
- }
- // On Mouse Up
- if (pointerInfo.type === PointerEventTypes.POINTERUP) {
- gizmoAxisCache.forEach((cache) => {
- cache.active = false;
- dragging = false;
- cache.gizmoMeshes.forEach((m) => {
- m.material = cache.dragBehavior.enabled ? cache.material : cache.disableMaterial;
- if (m.color) {
- m.color = cache.material.diffuseColor;
- }
- });
- });
- }
- }
- });
- return pointerObserver;
- }
- /**
- * Disposes of the gizmo
- */
- dispose() {
- this._rootMesh.dispose();
- if (this._beforeRenderObserver) {
- this.gizmoLayer.utilityLayerScene.onBeforeRenderObservable.remove(this._beforeRenderObserver);
- }
- }
- }
- /**
- * When enabled, any gizmo operation will perserve scaling sign. Default is off.
- * Only valid for TransformNode derived classes (Mesh, AbstractMesh, ...)
- */
- Gizmo.PreserveScaling = false;
- /**
- * There are 2 ways to preserve scaling: using mesh scaling or absolute scaling. Depending of hierarchy, non uniform scaling and LH or RH coordinates. One is preferable than the other.
- * If the scaling to be preserved is the local scaling, then set this value to false.
- * Default is true which means scaling to be preserved is absolute one (with hierarchy applied)
- */
- Gizmo.UseAbsoluteScaling = true;
- //# sourceMappingURL=gizmo.js.map
|