diff --git a/src/extras/gizmo/gizmo.js b/src/extras/gizmo/gizmo.js index e74a3529891..3fe22c154f6 100644 --- a/src/extras/gizmo/gizmo.js +++ b/src/extras/gizmo/gizmo.js @@ -456,12 +456,12 @@ class Gizmo extends EventHandler { _getSelection(x, y) { const start = this._camera.entity.getPosition(); const end = this._camera.screenToWorld(x, y, this._camera.farClip - this._camera.nearClip); - const dir = end.clone().sub(start).normalize(); + const dir = tmpV1.copy(end).sub(start).normalize(); const selection = []; for (let i = 0; i < this.intersectShapes.length; i++) { const shape = this.intersectShapes[i]; - if (shape.disabled) { + if (shape.disabled || !shape.entity.enabled) { continue; } diff --git a/src/extras/gizmo/scale-gizmo.js b/src/extras/gizmo/scale-gizmo.js index bc6222507e0..9ce63ca7f3d 100644 --- a/src/extras/gizmo/scale-gizmo.js +++ b/src/extras/gizmo/scale-gizmo.js @@ -18,6 +18,9 @@ const tmpV1 = new Vec3(); const tmpV2 = new Vec3(); const tmpQ1 = new Quat(); +// constants +const GLANCE_EPSILON = 0.98; + /** * Scaling gizmo. * @@ -105,6 +108,20 @@ class ScaleGizmo extends TransformGizmo { */ snapIncrement = 1; + /** + * Flips the planes to face the camera. + * + * @type {boolean} + */ + flipPlanes = true; + + /** + * The lower bound for scaling. + * + * @type {Vec3} + */ + lowerBoundScale = new Vec3(-Infinity, -Infinity, -Infinity); + /** * Creates a new ScaleGizmo object. * @@ -135,6 +152,10 @@ class ScaleGizmo extends TransformGizmo { this.on(TransformGizmo.EVENT_NODESDETACH, () => { this._nodeScales.clear(); }); + + this._app.on('prerender', () => { + this._planesLookAtCamera(); + }); } set coordSpace(value) { @@ -347,6 +368,29 @@ class ScaleGizmo extends TransformGizmo { this._shapes.xy[prop] = value; } + /** + * @private + */ + _planesLookAtCamera() { + tmpV1.cross(this._camera.entity.forward, this.root.right); + this._shapes.yz.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.yz.flipped = tmpV2.set(0, +(tmpV1.dot(this.root.forward) > 0), +(tmpV1.dot(this.root.up) > 0)); + } + + tmpV1.cross(this._camera.entity.forward, this.root.forward); + this._shapes.xy.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.xy.flipped = tmpV2.set(+(tmpV1.dot(this.root.up) > 0), +(tmpV1.dot(this.root.right) < 0), 0); + } + + tmpV1.cross(this._camera.entity.forward, this.root.up); + this._shapes.xz.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.xz.flipped = tmpV2.set(+(tmpV1.dot(this.root.forward) < 0), 0, +(tmpV1.dot(this.root.right) < 0)); + } + } + /** * @private */ @@ -368,7 +412,7 @@ class ScaleGizmo extends TransformGizmo { if (!scale) { continue; } - node.setLocalScale(scale.clone().mul(pointDelta)); + node.setLocalScale(tmpV1.copy(scale).mul(pointDelta).max(this.lowerBoundScale)); } } diff --git a/src/extras/gizmo/shape/plane-shape.js b/src/extras/gizmo/shape/plane-shape.js index 0cb81ece2cb..3cb984664ad 100644 --- a/src/extras/gizmo/shape/plane-shape.js +++ b/src/extras/gizmo/shape/plane-shape.js @@ -4,6 +4,8 @@ import { PlaneGeometry } from '../../../scene/geometry/plane-geometry.js'; import { TriData } from '../tri-data.js'; import { Shape } from './shape.js'; +const UPDATE_EPSILON = 1e-6; + class PlaneShape extends Shape { _cull = CULLFACE_NONE; @@ -11,6 +13,8 @@ class PlaneShape extends Shape { _gap = 0.1; + _flipped = new Vec3(); + constructor(device, options = {}) { super(device, options); @@ -21,21 +25,6 @@ class PlaneShape extends Shape { this._createPlane(); } - _getPosition() { - const offset = this._size / 2 + this._gap; - const position = new Vec3(offset, offset, offset); - position[this.axis] = 0; - return position; - } - - _createPlane() { - this._createRoot('plane'); - this._updateTransform(); - - // plane - this._addRenderMesh(this.entity, 'plane', this._shading); - } - set size(value) { this._size = value ?? 1; this._updateTransform(); @@ -54,6 +43,37 @@ class PlaneShape extends Shape { return this._gap; } + set flipped(value) { + if (this._flipped.distance(value) < UPDATE_EPSILON) { + return; + } + this._flipped.copy(value); + this.entity.setLocalPosition(this._getPosition()); + } + + get flipped() { + return this._flipped; + } + + _getPosition() { + const offset = this._size / 2 + this._gap; + const position = new Vec3( + this._flipped.x ? -offset : offset, + this._flipped.y ? -offset : offset, + this._flipped.z ? -offset : offset + ); + position[this.axis] = 0; + return position; + } + + _createPlane() { + this._createRoot('plane'); + this._updateTransform(); + + // plane + this._addRenderMesh(this.entity, 'plane', this._shading); + } + _updateTransform() { // intersect/render this.entity.setLocalPosition(this._getPosition()); diff --git a/src/extras/gizmo/translate-gizmo.js b/src/extras/gizmo/translate-gizmo.js index 07073364d56..0b841b59f95 100644 --- a/src/extras/gizmo/translate-gizmo.js +++ b/src/extras/gizmo/translate-gizmo.js @@ -24,6 +24,9 @@ const tmpV1 = new Vec3(); const tmpV2 = new Vec3(); const tmpQ1 = new Quat(); +// constants +const GLANCE_EPSILON = 0.98; + /** * Translation gizmo. * @@ -109,6 +112,13 @@ class TranslateGizmo extends TransformGizmo { */ snapIncrement = 1; + /** + * Flips the planes to face the camera. + * + * @type {boolean} + */ + flipPlanes = true; + /** * Creates a new TranslateGizmo object. * @@ -139,6 +149,10 @@ class TranslateGizmo extends TransformGizmo { this._nodeLocalPositions.clear(); this._nodePositions.clear(); }); + + this._app.on('prerender', () => { + this._planesLookAtCamera(); + }); } /** @@ -343,6 +357,29 @@ class TranslateGizmo extends TransformGizmo { this._shapes.xy[prop] = value; } + /** + * @private + */ + _planesLookAtCamera() { + tmpV1.cross(this._camera.entity.forward, this.root.right); + this._shapes.yz.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.yz.flipped = tmpV2.set(0, +(tmpV1.dot(this.root.forward) > 0), +(tmpV1.dot(this.root.up) > 0)); + } + + tmpV1.cross(this._camera.entity.forward, this.root.forward); + this._shapes.xy.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.xy.flipped = tmpV2.set(+(tmpV1.dot(this.root.up) > 0), +(tmpV1.dot(this.root.right) < 0), 0); + } + + tmpV1.cross(this._camera.entity.forward, this.root.up); + this._shapes.xz.entity.enabled = tmpV1.length() < GLANCE_EPSILON; + if (this.flipPlanes) { + this._shapes.xz.flipped = tmpV2.set(+(tmpV1.dot(this.root.forward) < 0), 0, +(tmpV1.dot(this.root.right) < 0)); + } + } + /** * @private */ @@ -361,11 +398,11 @@ class TranslateGizmo extends TransformGizmo { _setNodePositions(pointDelta) { for (let i = 0; i < this.nodes.length; i++) { const node = this.nodes[i]; - const pos = this._nodePositions.get(node); - if (!pos) { - continue; - } if (this._coordSpace === GIZMOSPACE_LOCAL) { + const pos = this._nodeLocalPositions.get(node); + if (!pos) { + continue; + } tmpV1.copy(pointDelta); node.parent?.getWorldTransform().getScale(tmpV2); tmpV2.x = 1 / tmpV2.x; @@ -373,9 +410,13 @@ class TranslateGizmo extends TransformGizmo { tmpV2.z = 1 / tmpV2.z; tmpQ1.copy(node.getLocalRotation()).transformVector(tmpV1, tmpV1); tmpV1.mul(tmpV2); - node.setLocalPosition(pos.clone().add(tmpV1)); + node.setLocalPosition(tmpV1.add(pos)); } else { - node.setPosition(pos.clone().add(pointDelta)); + const pos = this._nodePositions.get(node); + if (!pos) { + continue; + } + node.setPosition(tmpV1.copy(pointDelta).add(pos)); } }