@babylonjs/core
Version:
Getting started? Play directly with the Babylon.js API using our [playground](https://playground.babylonjs.com/). It also contains a lot of samples to learn how to use it.
241 lines • 11 kB
JavaScript
import { Observable } from "../Misc/observable.js";
import { TransformNode } from "../Meshes/transformNode.js";
import { Mesh } from "../Meshes/mesh.js";
import { CreateCylinder } from "../Meshes/Builders/cylinderBuilder.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 { Color3 } from "../Maths/math.color.js";
import { TmpVectors } from "../Maths/math.vector.js";
/**
* Single axis drag gizmo
*/
export class AxisDragGizmo extends Gizmo {
/** 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._hoverMaterial;
}
/** Material used to render when gizmo is disabled. typically grey.*/
get disableMaterial() {
return this._disableMaterial;
}
/**
* @internal
*/
static _CreateArrow(scene, material, thickness = 1, isCollider = false) {
const arrow = new TransformNode("arrow", scene);
const cylinder = CreateCylinder("cylinder", {
diameterTop: 0,
height: 0.075,
diameterBottom: 0.0375 * (1 + (thickness - 1) / 4),
tessellation: 96,
}, scene);
const line = CreateCylinder("cylinder", {
diameterTop: 0.005 * thickness,
height: 0.275,
diameterBottom: 0.005 * thickness,
tessellation: 96,
}, scene);
// Position arrow pointing in its drag axis
cylinder.parent = arrow;
cylinder.material = material;
cylinder.rotation.x = Math.PI / 2;
cylinder.position.z += 0.3;
line.parent = arrow;
line.material = material;
line.position.z += 0.275 / 2;
line.rotation.x = Math.PI / 2;
if (isCollider) {
line.visibility = 0;
cylinder.visibility = 0;
}
return arrow;
}
/**
* @internal
*/
static _CreateArrowInstance(scene, arrow) {
const instance = new TransformNode("arrow", scene);
for (const mesh of arrow.getChildMeshes()) {
const childInstance = mesh.createInstance(mesh.name);
childInstance.parent = instance;
}
return instance;
}
/**
* Creates an AxisDragGizmo
* @param dragAxis The axis which the gizmo will be able to drag on
* @param color The color of the gizmo
* @param gizmoLayer The utility layer the gizmo will be added to
* @param parent
* @param thickness display gizmo axis thickness
* @param hoverColor The color of the gizmo when hovering over and dragging
* @param disableColor The Color of the gizmo when its disabled
*/
constructor(dragAxis, color = Color3.Gray(), gizmoLayer = UtilityLayerRenderer.DefaultUtilityLayer, parent = null, thickness = 1, hoverColor = Color3.Yellow(), disableColor = Color3.Gray()) {
super(gizmoLayer);
this._pointerObserver = null;
/**
* Drag distance in babylon units that the gizmo will snap to when dragged (Default: 0)
*/
this.snapDistance = 0;
/**
* Event that fires each time the gizmo snaps to a new location.
* * snapDistance is the change in distance
*/
this.onSnapObservable = new Observable();
this._isEnabled = true;
this._parent = null;
this._dragging = false;
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 = hoverColor;
this._disableMaterial = new StandardMaterial("", gizmoLayer.utilityLayerScene);
this._disableMaterial.diffuseColor = disableColor;
this._disableMaterial.alpha = 0.4;
// Build Mesh + Collider
const arrow = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness);
const collider = AxisDragGizmo._CreateArrow(gizmoLayer.utilityLayerScene, this._coloredMaterial, thickness + 4, true);
// Add to Root Node
this._gizmoMesh = new Mesh("", gizmoLayer.utilityLayerScene);
this._gizmoMesh.addChild(arrow);
this._gizmoMesh.addChild(collider);
this._gizmoMesh.lookAt(this._rootMesh.position.add(dragAxis));
this._gizmoMesh.scaling.scaleInPlace(1 / 3);
this._gizmoMesh.parent = this._rootMesh;
let currentSnapDragDistance = 0;
const tmpSnapEvent = { snapDistance: 0 };
// Add drag behavior to handle events when the gizmo is dragged
this.dragBehavior = new PointerDragBehavior({ dragAxis: dragAxis });
this.dragBehavior.moveAttached = false;
this.dragBehavior.updateDragPlane = false;
this._rootMesh.addBehavior(this.dragBehavior);
this.dragBehavior.onDragObservable.add((event) => {
if (this.attachedNode) {
// Keep world translation and use it to update world transform
// if the node has parent, the local transform properties (position, rotation, scale)
// will be recomputed in _matrixChanged function
let matrixChanged = false;
// Snapping logic
if (this.snapDistance == 0) {
this.attachedNode.getWorldMatrix().getTranslationToRef(TmpVectors.Vector3[2]);
TmpVectors.Vector3[2].addInPlace(event.delta);
if (this.dragBehavior.validateDrag(TmpVectors.Vector3[2])) {
if (this.attachedNode.position) {
// Required for nodes like lights
this.attachedNode.position.addInPlaceFromFloats(event.delta.x, event.delta.y, event.delta.z);
}
// use _worldMatrix to not force a matrix update when calling GetWorldMatrix especially with Cameras
this.attachedNode.getWorldMatrix().addTranslationFromFloats(event.delta.x, event.delta.y, event.delta.z);
this.attachedNode.updateCache();
matrixChanged = true;
}
}
else {
currentSnapDragDistance += event.dragDistance;
if (Math.abs(currentSnapDragDistance) > this.snapDistance) {
const dragSteps = Math.floor(Math.abs(currentSnapDragDistance) / this.snapDistance);
currentSnapDragDistance = currentSnapDragDistance % this.snapDistance;
event.delta.normalizeToRef(TmpVectors.Vector3[1]);
TmpVectors.Vector3[1].scaleInPlace(this.snapDistance * dragSteps);
this.attachedNode.getWorldMatrix().getTranslationToRef(TmpVectors.Vector3[2]);
TmpVectors.Vector3[2].addInPlace(TmpVectors.Vector3[1]);
if (this.dragBehavior.validateDrag(TmpVectors.Vector3[2])) {
this.attachedNode.getWorldMatrix().addTranslationFromFloats(TmpVectors.Vector3[1].x, TmpVectors.Vector3[1].y, TmpVectors.Vector3[1].z);
this.attachedNode.updateCache();
tmpSnapEvent.snapDistance = this.snapDistance * dragSteps * Math.sign(currentSnapDragDistance);
this.onSnapObservable.notifyObservers(tmpSnapEvent);
matrixChanged = true;
}
}
}
if (matrixChanged) {
this._matrixChanged();
}
}
});
this.dragBehavior.onDragStartObservable.add(() => {
this._dragging = true;
});
this.dragBehavior.onDragEndObservable.add(() => {
this._dragging = false;
});
const light = gizmoLayer._getSharedGizmoLight();
light.includedOnlyMeshes = light.includedOnlyMeshes.concat(this._rootMesh.getChildMeshes(false));
const cache = {
gizmoMeshes: arrow.getChildMeshes(),
colliderMeshes: collider.getChildMeshes(),
material: this._coloredMaterial,
hoverMaterial: this._hoverMaterial,
disableMaterial: this._disableMaterial,
active: false,
dragBehavior: this.dragBehavior,
};
this._parent?.addToAxisCache(collider, cache);
this._pointerObserver = gizmoLayer.utilityLayerScene.onPointerObservable.add((pointerInfo) => {
if (this._customMeshSet) {
return;
}
this._isHovered = !!(cache.colliderMeshes.indexOf(pointerInfo?.pickInfo?.pickedMesh) != -1);
if (!this._parent) {
const material = this.dragBehavior.enabled ? (this._isHovered || this._dragging ? this._hoverMaterial : this._coloredMaterial) : this._disableMaterial;
this._setGizmoMeshMaterial(cache.gizmoMeshes, material);
}
});
this.dragBehavior.onEnabledObservable.add((newState) => {
this._setGizmoMeshMaterial(cache.gizmoMeshes, newState ? cache.material : cache.disableMaterial);
});
}
_attachedNodeChanged(value) {
if (this.dragBehavior) {
this.dragBehavior.enabled = value ? true : false;
}
}
/**
* If the gizmo is enabled
*/
set isEnabled(value) {
this._isEnabled = value;
if (!value) {
this.attachedMesh = null;
this.attachedNode = null;
}
else {
if (this._parent) {
this.attachedMesh = this._parent.attachedMesh;
this.attachedNode = this._parent.attachedNode;
}
}
}
get isEnabled() {
return this._isEnabled;
}
/**
* Disposes of the gizmo
*/
dispose() {
this.onSnapObservable.clear();
this.gizmoLayer.utilityLayerScene.onPointerObservable.remove(this._pointerObserver);
this.dragBehavior.detach();
if (this._gizmoMesh) {
this._gizmoMesh.dispose();
}
const mats = [this._coloredMaterial, this._hoverMaterial, this._disableMaterial];
for (const matl of mats) {
if (matl) {
matl.dispose();
}
}
super.dispose();
}
}
//# sourceMappingURL=axisDragGizmo.js.map