import { Observer, Observable } from "../Misc/observable"; import { Nullable } from "../types"; import { PointerInfo } from "../Events/pointerEvents"; import { Vector3, Color3 } from "../Maths/math"; import { AbstractMesh } from "../Meshes/abstractMesh"; import { Mesh } from "../Meshes/mesh"; import { LinesMesh } from "../Meshes/linesMesh"; import { BoxBuilder } from "../Meshes/Builders/boxBuilder"; import { CylinderBuilder } from "../Meshes/Builders/cylinderBuilder"; import { StandardMaterial } from "../Materials/standardMaterial"; import { PointerDragBehavior } from "../Behaviors/Meshes/pointerDragBehavior"; import { _TimeToken } from "../Instrumentation/timeToken"; import { _DepthCullingState, _StencilState, _AlphaState } from "../States/index"; import { Gizmo } from "./gizmo"; import { UtilityLayerRenderer } from "../Rendering/utilityLayerRenderer"; import { ScaleGizmo } from "./scaleGizmo"; /** * Single axis scale gizmo */ export class AxisScaleGizmo extends Gizmo { /** * Drag behavior responsible for the gizmos dragging interactions */ public dragBehavior: PointerDragBehavior; private _pointerObserver: Nullable> = null; /** * Scale distance in babylon units that the gizmo will snap to when dragged (Default: 0) */ public snapDistance = 0; /** * Event that fires each time the gizmo snaps to a new location. * * snapDistance is the the change in distance */ public onSnapObservable = new Observable<{ snapDistance: number }>(); /** * If the scaling operation should be done on all axis (default: false) */ public uniformScaling = false; private _isEnabled: boolean = true; private _parent: Nullable = null; private _arrow: AbstractMesh; private _coloredMaterial: StandardMaterial; private _hoverMaterial: StandardMaterial; /** * Creates an AxisScaleGizmo * @param gizmoLayer The utility layer the gizmo will be added to * @param dragAxis The axis which the gizmo will be able to scale on * @param color The color of the gizmo */ constructor(dragAxis: Vector3, color: Color3 = Color3.Gray(), gizmoLayer: UtilityLayerRenderer = UtilityLayerRenderer.DefaultUtilityLayer, parent: Nullable = null) { super(gizmoLayer); this._parent = parent; // Create Material this._coloredMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene); this._coloredMaterial.diffuseColor = color; this._coloredMaterial.specularColor = color.subtract(new Color3(0.1, 0.1, 0.1)); this._hoverMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene); this._hoverMaterial.diffuseColor = color.add(new Color3(0.3, 0.3, 0.3)); // Build mesh on root node this._arrow = new AbstractMesh("", gizmoLayer.utilityLayerScene); var arrowMesh = BoxBuilder.CreateBox("yPosMesh", { size: 0.4 }, gizmoLayer.utilityLayerScene); var arrowTail = CylinderBuilder.CreateCylinder("cylinder", { diameterTop: 0.005, height: 0.275, diameterBottom: 0.005, tessellation: 96 }, gizmoLayer.utilityLayerScene); arrowTail.material = this._coloredMaterial; this._arrow.addChild(arrowMesh); this._arrow.addChild(arrowTail); // Position arrow pointing in its drag axis arrowMesh.scaling.scaleInPlace(0.1); arrowMesh.material = this._coloredMaterial; arrowMesh.rotation.x = Math.PI / 2; arrowMesh.position.z += 0.3; arrowTail.position.z += 0.275 / 2; arrowTail.rotation.x = Math.PI / 2; this._arrow.lookAt(this._rootMesh.position.add(dragAxis)); this._rootMesh.addChild(this._arrow); this._arrow.scaling.scaleInPlace(1 / 3); // Add drag behavior to handle events when the gizmo is dragged this.dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis }); this.dragBehavior.moveAttached = false; this._rootMesh.addBehavior(this.dragBehavior); var currentSnapDragDistance = 0; var tmpVector = new Vector3(); var tmpSnapEvent = { snapDistance: 0 }; this.dragBehavior.onDragObservable.add((event) => { if (this.attachedMesh) { // Drag strength is modified by the scale of the gizmo (eg. for small objects like boombox the strength will be increased to match the behavior of larger objects) var dragStrength = event.dragDistance * ((this.scaleRatio * 3) / this._rootMesh.scaling.length()); // Snapping logic var snapped = false; var dragSteps = 0; if (this.uniformScaling) { this.attachedMesh.scaling.normalizeToRef(tmpVector); if (tmpVector.y < 0) { tmpVector.scaleInPlace(-1); } } else { tmpVector.copyFrom(dragAxis); } if (this.snapDistance == 0) { tmpVector.scaleToRef(dragStrength, tmpVector); } else { currentSnapDragDistance += dragStrength; if (Math.abs(currentSnapDragDistance) > this.snapDistance) { dragSteps = Math.floor(Math.abs(currentSnapDragDistance) / this.snapDistance); if (currentSnapDragDistance < 0) { dragSteps *= -1; } currentSnapDragDistance = currentSnapDragDistance % this.snapDistance; tmpVector.scaleToRef(this.snapDistance * dragSteps, tmpVector); snapped = true; } else { tmpVector.scaleInPlace(0); } } this.attachedMesh.scaling.addInPlace(tmpVector); if (snapped) { tmpSnapEvent.snapDistance = this.snapDistance * dragSteps; this.onSnapObservable.notifyObservers(tmpSnapEvent); } } }); this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => { if (this._customMeshSet) { return; } var isHovered = pointerInfo.pickInfo && (this._rootMesh.getChildMeshes().indexOf(pointerInfo.pickInfo.pickedMesh) != -1); var material = isHovered ? this._hoverMaterial : this._coloredMaterial; this._rootMesh.getChildMeshes().forEach((m) => { m.material = material; if ((m).color) { (m).color = material.diffuseColor; } }); }); var light = gizmoLayer._getSharedGizmoLight(); light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes()); } protected _attachedMeshChanged(value: Nullable) { if (this.dragBehavior) { this.dragBehavior.enabled = value ? true : false; } } /** * If the gizmo is enabled */ public set isEnabled(value: boolean) { this._isEnabled = value; if (!value) { this.attachedMesh = null; } else { if (this._parent) { this.attachedMesh = this._parent.attachedMesh; } } } public get isEnabled(): boolean { return this._isEnabled; } /** * Disposes of the gizmo */ public dispose() { this.onSnapObservable.clear(); this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver); this.dragBehavior.detach(); if (this._arrow) { this._arrow.dispose(); } [this._coloredMaterial, this._hoverMaterial].forEach((matl) => { if (matl) { matl.dispose(); } }); super.dispose(); } /** * 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 * @param useGizmoMaterial If the gizmo's default material should be used (default: false) */ public setCustomMesh(mesh: Mesh, useGizmoMaterial: boolean = false) { super.setCustomMesh(mesh); if (useGizmoMaterial) { this._rootMesh.getChildMeshes().forEach((m) => { m.material = this._coloredMaterial; if ((m).color) { (m).color = this._coloredMaterial.diffuseColor; } }); this._customMeshSet = false; } } }