UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

322 lines (321 loc) 8.99 kB
import { Vec3 } from "../../core/math/vec3.js"; import { Quat } from "../../core/math/quat.js"; import { TransformGizmo } from "./transform-gizmo.js"; import { BoxShape } from "./shape/box-shape.js"; import { PlaneShape } from "./shape/plane-shape.js"; import { BoxLineShape } from "./shape/boxline-shape.js"; const v1 = new Vec3(); const v2 = new Vec3(); const point = new Vec3(); const delta = new Vec3(); const q = new Quat(); const GLANCE_EPSILON = 0.01; class ScaleGizmo extends TransformGizmo { _shapes = { xyz: new BoxShape(this._device, { axis: "xyz", layers: [this._layer.id], defaultColor: this._theme.shapeBase.xyz, hoverColor: this._theme.shapeHover.xyz, disabledColor: this._theme.disabled }), yz: new PlaneShape(this._device, { axis: "x", layers: [this._layer.id], rotation: new Vec3(0, 0, -90), defaultColor: this._theme.shapeBase.x, hoverColor: this._theme.shapeHover.x, disabledColor: this._theme.disabled, depth: 1 }), xz: new PlaneShape(this._device, { axis: "y", layers: [this._layer.id], rotation: new Vec3(0, 0, 0), defaultColor: this._theme.shapeBase.y, hoverColor: this._theme.shapeHover.y, disabledColor: this._theme.disabled, depth: 1 }), xy: new PlaneShape(this._device, { axis: "z", layers: [this._layer.id], rotation: new Vec3(90, 0, 0), defaultColor: this._theme.shapeBase.z, hoverColor: this._theme.shapeHover.z, disabledColor: this._theme.disabled, depth: 1 }), x: new BoxLineShape(this._device, { axis: "x", layers: [this._layer.id], rotation: new Vec3(0, 0, -90), defaultColor: this._theme.shapeBase.x, hoverColor: this._theme.shapeHover.x, disabledColor: this._theme.disabled }), y: new BoxLineShape(this._device, { axis: "y", layers: [this._layer.id], rotation: new Vec3(0, 0, 0), defaultColor: this._theme.shapeBase.y, hoverColor: this._theme.shapeHover.y, disabledColor: this._theme.disabled }), z: new BoxLineShape(this._device, { axis: "z", layers: [this._layer.id], rotation: new Vec3(90, 0, 0), defaultColor: this._theme.shapeBase.z, hoverColor: this._theme.shapeHover.z, disabledColor: this._theme.disabled }) }; _coordSpace = "local"; _nodeScales = /* @__PURE__ */ new Map(); _uniform = false; snapIncrement = 1; flipPlanes = true; lowerBoundScale = new Vec3(-Infinity, -Infinity, -Infinity); constructor(camera, layer) { super(camera, layer, "gizmo:scale"); this._createTransform(); this.on(TransformGizmo.EVENT_TRANSFORMSTART, () => { this._storeNodeScales(); this._drag(true); }); this.on(TransformGizmo.EVENT_TRANSFORMMOVE, (point2) => { const scaleDelta = delta.copy(point2).sub(this._selectionStartPoint); if (this.snap) { scaleDelta.mulScalar(1 / this.snapIncrement); scaleDelta.round(); scaleDelta.mulScalar(this.snapIncrement); } scaleDelta.mulScalar(1 / this._scale); this._setNodeScales(scaleDelta.add(Vec3.ONE)); }); this.on(TransformGizmo.EVENT_TRANSFORMEND, () => { this._drag(false); }); this.on(TransformGizmo.EVENT_NODESDETACH, () => { this._nodeScales.clear(); }); } set coordSpace(value) { } get coordSpace() { return this._coordSpace; } set uniform(value) { this._uniform = value ?? this._uniform; } get uniform() { return this._uniform; } set axisGap(value) { this._setArrowProp("gap", value); } get axisGap() { return this._shapes.x.gap; } set axisLineThickness(value) { this._setArrowProp("lineThickness", value); } get axisLineThickness() { return this._shapes.x.lineThickness; } set axisLineLength(value) { this._setArrowProp("lineLength", value); } get axisLineLength() { return this._shapes.x.lineLength; } set axisLineTolerance(value) { this._setArrowProp("tolerance", value); } get axisLineTolerance() { return this._shapes.x.tolerance; } set axisBoxSize(value) { this._setArrowProp("boxSize", value); } get axisBoxSize() { return this._shapes.x.boxSize; } set axisPlaneSize(value) { this._setPlaneProp("size", value); } get axisPlaneSize() { return this._shapes.yz.size; } set axisPlaneGap(value) { this._setPlaneProp("gap", value); } get axisPlaneGap() { return this._shapes.yz.gap; } set axisCenterSize(value) { this._shapes.xyz.size = value; } get axisCenterSize() { return this._shapes.xyz.size; } set flipShapes(value) { this.flipPlanes = value; } get flipShapes() { return this.flipPlanes; } _setArrowProp(prop, value) { this._shapes.x[prop] = value; this._shapes.y[prop] = value; this._shapes.z[prop] = value; } _setPlaneProp(prop, value) { this._shapes.yz[prop] = value; this._shapes.xz[prop] = value; this._shapes.xy[prop] = value; } _shapesLookAtCamera() { const cameraDir = this.cameraDir; let changed = false; let dot, enabled; dot = cameraDir.dot(this.root.right); enabled = 1 - Math.abs(dot) > GLANCE_EPSILON; if (this._shapes.x.entity.enabled !== enabled) { this._shapes.x.entity.enabled = enabled; changed = true; } dot = cameraDir.dot(this.root.up); enabled = 1 - Math.abs(dot) > GLANCE_EPSILON; if (this._shapes.y.entity.enabled !== enabled) { this._shapes.y.entity.enabled = enabled; changed = true; } dot = cameraDir.dot(this.root.forward); enabled = 1 - Math.abs(dot) > GLANCE_EPSILON; if (this._shapes.z.entity.enabled !== enabled) { this._shapes.z.entity.enabled = enabled; changed = true; } let flipped; v1.cross(cameraDir, this.root.right); enabled = 1 - v1.length() > GLANCE_EPSILON; if (this._shapes.yz.entity.enabled !== enabled) { this._shapes.yz.entity.enabled = enabled; changed = true; } flipped = this.flipPlanes ? v2.set(0, +(v1.dot(this.root.forward) < 0), +(v1.dot(this.root.up) < 0)) : Vec3.ZERO; if (!this._shapes.yz.flipped.equals(flipped)) { this._shapes.yz.flipped = flipped; changed = true; } v1.cross(cameraDir, this.root.forward); enabled = 1 - v1.length() > GLANCE_EPSILON; if (this._shapes.xy.entity.enabled !== enabled) { this._shapes.xy.entity.enabled = enabled; changed = true; } flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.up) < 0), +(v1.dot(this.root.right) > 0), 0) : Vec3.ZERO; if (!this._shapes.xy.flipped.equals(flipped)) { this._shapes.xy.flipped = flipped; changed = true; } v1.cross(cameraDir, this.root.up); enabled = 1 - v1.length() > GLANCE_EPSILON; if (this._shapes.xz.entity.enabled !== enabled) { this._shapes.xz.entity.enabled = enabled; changed = true; } flipped = this.flipPlanes ? v2.set(+(v1.dot(this.root.forward) > 0), 0, +(v1.dot(this.root.right) > 0)) : Vec3.ZERO; if (!this._shapes.xz.flipped.equals(flipped)) { this._shapes.xz.flipped = flipped; changed = true; } if (changed) { this._renderUpdate = true; } } _drag(state) { for (const axis in this._shapes) { const shape = this._shapes[axis]; switch (this.dragMode) { case "show": { continue; } case "hide": { shape.visible = !state; continue; } case "selected": { if (this._selectedAxis === "xyz") { shape.visible = state ? axis.length === 1 : true; continue; } if (this._selectedIsPlane) { shape.visible = state ? axis.length === 1 && !axis.includes(this._selectedAxis) : true; continue; } shape.visible = state ? axis === this._selectedAxis : true; continue; } } } this._renderUpdate = true; } _storeNodeScales() { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; this._nodeScales.set(node, node.getLocalScale().clone()); } } _setNodeScales(scaleDelta) { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; const scale = this._nodeScales.get(node); if (!scale) { continue; } node.setLocalScale(v1.copy(scale).mul(scaleDelta).max(this.lowerBoundScale)); } } _screenToPoint(x, y) { const gizmoPos = this.root.getLocalPosition(); const mouseWPos = this._camera.screenToWorld(x, y, 1); const axis = this._selectedAxis; const isPlane = this._selectedIsPlane; const ray = this._createRay(mouseWPos); const plane = this._createPlane(axis, axis === "xyz", !isPlane); if (!plane.intersectsRay(ray, point)) { return point; } if (axis === "xyz") { const projDir = v2.add2(this._camera.entity.up, this._camera.entity.right).normalize(); const dir = v1.sub2(point, gizmoPos); const v = dir.length() * dir.normalize().dot(projDir); point.set(v, v, v); return point; } q.copy(this._rootStartRot).invert().transformVector(point, point); if (!isPlane) { this._projectToAxis(point, axis); } if (this._uniform && isPlane) { v1.set(1, 1, 1); v1[axis] = 0; point.copy(v1.mulScalar(v1.dot(point))); point[axis] = 0; } return point; } prerender() { super.prerender(); if (!this.enabled) { return; } this._shapesLookAtCamera(); } } export { ScaleGizmo };