Skip to content

Commit 3cc63fa

Browse files
authored
Gizmo updates (#7887)
* feat: refactored app events to be owned by base gizmo class * feat: added app event cleanup on destroy * feat: added support for updating camera and layer * fix: simplify layer addition to camera layers * fix: replace constant layer name with default value in createLayer method * fix: set default layer index to the end of the layer list in createLayer method * fix: separated out logic for facing dir vs camera dir
1 parent 371394e commit 3cc63fa

File tree

5 files changed

+171
-59
lines changed

5 files changed

+171
-59
lines changed

src/extras/gizmo/gizmo.js

Lines changed: 94 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { GIZMOSPACE_LOCAL, GIZMOSPACE_WORLD } from './constants.js';
1616
* @import { GraphNode } from '../../scene/graph-node.js'
1717
* @import { GraphicsDevice } from '../../platform/graphics/graphics-device.js'
1818
* @import { MeshInstance } from '../../scene/mesh-instance.js'
19+
* @import { EventHandle } from '../../core/event-handle.js'
1920
* @import { Shape } from './shape/shape.js'
2021
*/
2122

@@ -27,7 +28,6 @@ const tmpM2 = new Mat4();
2728
const tmpR1 = new Ray();
2829

2930
// constants
30-
const LAYER_NAME = 'Gizmo';
3131
const MIN_SCALE = 1e-4;
3232
const PERS_SCALE_RATIO = 0.3;
3333
const ORTHO_SCALE_RATIO = 0.32;
@@ -187,6 +187,14 @@ class Gizmo extends EventHandler {
187187
*/
188188
_device;
189189

190+
/**
191+
* Internal list of app event handles for the gizmo.
192+
*
193+
* @type {EventHandle[]}
194+
* @protected
195+
*/
196+
_handles = [];
197+
190198
/**
191199
* Internal reference to camera component to view the gizmo.
192200
*
@@ -232,14 +240,14 @@ class Gizmo extends EventHandler {
232240
* @param {number} [layerIndex] - The layer index. Defaults to the end of the layer list.
233241
* @returns {Layer} The new layer.
234242
*/
235-
static createLayer(app, layerName = LAYER_NAME, layerIndex) {
243+
static createLayer(app, layerName = 'Gizmo', layerIndex = app.scene.layers.layerList.length) {
236244
const layer = new Layer({
237245
name: layerName,
238246
clearDepthBuffer: true,
239247
opaqueSortMode: SORTMODE_NONE,
240248
transparentSortMode: SORTMODE_NONE
241249
});
242-
app.scene.layers.insert(layer, layerIndex ?? app.scene.layers.layerList.length);
250+
app.scene.layers.insert(layer, layerIndex);
243251
return layer;
244252
}
245253

@@ -256,12 +264,12 @@ class Gizmo extends EventHandler {
256264
Debug.assert(camera instanceof CameraComponent, 'Incorrect parameters for Gizmos\'s constructor. Use new Gizmo(camera, layer)');
257265
super();
258266

267+
this._layer = layer;
259268
this._camera = camera;
260-
this._app = camera.system.app;
261-
this._device = this._app.graphicsDevice;
269+
this._camera.layers = this._camera.layers.concat(this._layer.id);
262270

263-
this._layer = layer;
264-
camera.layers = camera.layers.concat(layer.id);
271+
this._app = this._camera.system.app;
272+
this._device = this._app.graphicsDevice;
265273

266274
this.root = new Entity('gizmo');
267275
this._app.root.addChild(this.root);
@@ -277,24 +285,57 @@ class Gizmo extends EventHandler {
277285
this._device.canvas.addEventListener('pointermove', this._onPointerMove);
278286
this._device.canvas.addEventListener('pointerup', this._onPointerUp);
279287

280-
this._app.on('update', () => {
281-
this._updatePosition();
282-
this._updateRotation();
283-
this._updateScale();
284-
});
285-
286-
this._app.on('destroy', () => this.destroy());
288+
this._handles.push(this._app.on('prerender', () => this.prerender()));
289+
this._handles.push(this._app.on('update', () => this.update()));
290+
this._handles.push(this._app.on('destroy', () => this.destroy()));
287291
}
288292

289293
/**
290294
* Sets the gizmo render layer.
291295
*
296+
* @param {Layer} layer - The layer to render the gizmo.
297+
*/
298+
set layer(layer) {
299+
if (this._layer === layer) {
300+
return;
301+
}
302+
this._camera.layers = this._camera.layers.filter(id => id !== this._layer.id);
303+
this._layer = layer;
304+
this._camera.layers = this._camera.layers.concat(this._layer.id);
305+
}
306+
307+
/**
308+
* Gets the gizmo render layer.
309+
*
292310
* @type {Layer}
293311
*/
294312
get layer() {
295313
return this._layer;
296314
}
297315

316+
/**
317+
* Sets the camera component to view the gizmo.
318+
*
319+
* @type {CameraComponent} camera - The camera component.
320+
*/
321+
set camera(camera) {
322+
if (this._camera === camera) {
323+
return;
324+
}
325+
this._camera.layers = this._camera.layers.filter(id => id !== this._layer.id);
326+
this._camera = camera;
327+
this._camera.layers = this._camera.layers.concat(this._layer.id);
328+
}
329+
330+
/**
331+
* Gets the camera component to view the gizmo.
332+
*
333+
* @type {CameraComponent} The camera component.
334+
*/
335+
get camera() {
336+
return this._camera;
337+
}
338+
298339
/**
299340
* Sets the gizmo coordinate space. Can be:
300341
*
@@ -342,7 +383,7 @@ class Gizmo extends EventHandler {
342383
* @type {Vec3}
343384
* @protected
344385
*/
345-
get facing() {
386+
get facingDir() {
346387
if (this._camera.projection === PROJECTION_PERSPECTIVE) {
347388
const gizmoPos = this.root.getPosition();
348389
const cameraPos = this._camera.entity.getPosition();
@@ -351,6 +392,16 @@ class Gizmo extends EventHandler {
351392
return tmpV2.copy(this._camera.entity.forward).mulScalar(-1);
352393
}
353394

395+
/**
396+
* @type {Vec3}
397+
* @protected
398+
*/
399+
get cameraDir() {
400+
const cameraPos = this._camera.entity.getPosition();
401+
const gizmoPos = this.root.getPosition();
402+
return tmpV2.sub2(cameraPos, gizmoPos).normalize();
403+
}
404+
354405
/**
355406
* @param {PointerEvent} e - The pointer event.
356407
* @private
@@ -567,6 +618,31 @@ class Gizmo extends EventHandler {
567618
this.nodes = [];
568619
}
569620

621+
/**
622+
* Pre-render method. This is called before the gizmo is rendered.
623+
*
624+
* @example
625+
* const gizmo = new pc.Gizmo(camera, layer);
626+
* gizmo.attach([boxA, boxB]);
627+
* gizmo.prerender();
628+
*/
629+
prerender() {
630+
}
631+
632+
/**
633+
* Updates the gizmo position, rotation, and scale.
634+
*
635+
* @example
636+
* const gizmo = new pc.Gizmo(camera, layer);
637+
* gizmo.attach([boxA, boxB]);
638+
* gizmo.update();
639+
*/
640+
update() {
641+
this._updatePosition();
642+
this._updateRotation();
643+
this._updateScale();
644+
}
645+
570646
/**
571647
* Detaches all graph nodes and destroys the gizmo instance.
572648
*
@@ -582,7 +658,10 @@ class Gizmo extends EventHandler {
582658
this._device.canvas.removeEventListener('pointermove', this._onPointerMove);
583659
this._device.canvas.removeEventListener('pointerup', this._onPointerUp);
584660

661+
this._handles.forEach(handle => handle.off());
662+
585663
this.root.destroy();
664+
586665
}
587666
}
588667

src/extras/gizmo/rotate-gizmo.js

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -219,17 +219,6 @@ class RotateGizmo extends TransformGizmo {
219219
this._nodeRotations.clear();
220220
this._nodeOffsets.clear();
221221
});
222-
223-
this._app.on('prerender', () => {
224-
this._shapesLookAtCamera();
225-
226-
if (this._dragging) {
227-
const gizmoPos = this.root.getPosition();
228-
this._drawGuideAngleLine(gizmoPos, this._selectedAxis,
229-
this._guideAngleStart, this._guideAngleStartColor);
230-
this._drawGuideAngleLine(gizmoPos, this._selectedAxis, this._guideAngleEnd);
231-
}
232-
});
233222
}
234223

235224
/**
@@ -357,7 +346,7 @@ class RotateGizmo extends TransformGizmo {
357346
const isFacing = axis === GIZMOAXIS_FACE;
358347

359348
if (isFacing) {
360-
tmpV1.copy(this.facing);
349+
tmpV1.copy(this.facingDir);
361350
} else {
362351
tmpV1.set(0, 0, 0);
363352
tmpV1[axis] = 1;
@@ -409,7 +398,7 @@ class RotateGizmo extends TransformGizmo {
409398
}
410399

411400
// axes shapes
412-
const facingDir = tmpV1.copy(this.facing);
401+
const facingDir = tmpV1.copy(this.facingDir);
413402
tmpQ1.copy(this.root.getRotation()).invert().transformVector(facingDir, facingDir);
414403
let angle = Math.atan2(facingDir.z, facingDir.y) * math.RAD_TO_DEG;
415404
this._shapes.x.entity.setLocalEulerAngles(0, angle - 90, -90);
@@ -531,7 +520,7 @@ class RotateGizmo extends TransformGizmo {
531520
let angle = 0;
532521

533522
// calculate angle
534-
const facingDir = tmpV2.copy(this.facing);
523+
const facingDir = tmpV2.copy(this.facingDir);
535524
const facingDot = plane.normal.dot(facingDir);
536525
if (this.orbitRotation || Math.abs(facingDot) > FACING_THRESHOLD) {
537526
// plane facing camera so based on mouse position around gizmo
@@ -559,6 +548,26 @@ class RotateGizmo extends TransformGizmo {
559548

560549
return angle;
561550
}
551+
552+
/**
553+
* @override
554+
*/
555+
prerender() {
556+
super.prerender();
557+
558+
if (!this.root.enabled) {
559+
return;
560+
}
561+
562+
this._shapesLookAtCamera();
563+
564+
if (this._dragging) {
565+
const gizmoPos = this.root.getPosition();
566+
this._drawGuideAngleLine(gizmoPos, this._selectedAxis,
567+
this._guideAngleStart, this._guideAngleStartColor);
568+
this._drawGuideAngleLine(gizmoPos, this._selectedAxis, this._guideAngleEnd);
569+
}
570+
}
562571
}
563572

564573
export { RotateGizmo };

src/extras/gizmo/scale-gizmo.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -184,10 +184,6 @@ class ScaleGizmo extends TransformGizmo {
184184
this.on(TransformGizmo.EVENT_NODESDETACH, () => {
185185
this._nodeScales.clear();
186186
});
187-
188-
this._app.on('prerender', () => {
189-
this._shapesLookAtCamera();
190-
});
191187
}
192188

193189
set coordSpace(value) {
@@ -404,37 +400,37 @@ class ScaleGizmo extends TransformGizmo {
404400
* @private
405401
*/
406402
_shapesLookAtCamera() {
407-
const facingDir = this.facing;
403+
const cameraDir = this.cameraDir;
408404

409405
// axes
410-
let dot = facingDir.dot(this.root.right);
406+
let dot = cameraDir.dot(this.root.right);
411407
this._shapes.x.entity.enabled = Math.abs(dot) < GLANCE_EPSILON;
412408
if (this.flipShapes) {
413409
this._shapes.x.flipped = dot < 0;
414410
}
415-
dot = facingDir.dot(this.root.up);
411+
dot = cameraDir.dot(this.root.up);
416412
this._shapes.y.entity.enabled = Math.abs(dot) < GLANCE_EPSILON;
417413
if (this.flipShapes) {
418414
this._shapes.y.flipped = dot < 0;
419415
}
420-
dot = facingDir.dot(this.root.forward);
416+
dot = cameraDir.dot(this.root.forward);
421417
this._shapes.z.entity.enabled = Math.abs(dot) < GLANCE_EPSILON;
422418
if (this.flipShapes) {
423419
this._shapes.z.flipped = dot > 0;
424420
}
425421

426422
// planes
427-
tmpV1.cross(facingDir, this.root.right);
423+
tmpV1.cross(cameraDir, this.root.right);
428424
this._shapes.yz.entity.enabled = tmpV1.length() < GLANCE_EPSILON;
429425
if (this.flipShapes) {
430426
this._shapes.yz.flipped = tmpV2.set(0, +(tmpV1.dot(this.root.forward) < 0), +(tmpV1.dot(this.root.up) < 0));
431427
}
432-
tmpV1.cross(facingDir, this.root.forward);
428+
tmpV1.cross(cameraDir, this.root.forward);
433429
this._shapes.xy.entity.enabled = tmpV1.length() < GLANCE_EPSILON;
434430
if (this.flipShapes) {
435431
this._shapes.xy.flipped = tmpV2.set(+(tmpV1.dot(this.root.up) < 0), +(tmpV1.dot(this.root.right) > 0), 0);
436432
}
437-
tmpV1.cross(facingDir, this.root.up);
433+
tmpV1.cross(cameraDir, this.root.up);
438434
this._shapes.xz.entity.enabled = tmpV1.length() < GLANCE_EPSILON;
439435
if (this.flipShapes) {
440436
this._shapes.xz.flipped = tmpV2.set(+(tmpV1.dot(this.root.forward) > 0), 0, +(tmpV1.dot(this.root.right) > 0));
@@ -532,6 +528,19 @@ class ScaleGizmo extends TransformGizmo {
532528

533529
return point;
534530
}
531+
532+
/**
533+
* @override
534+
*/
535+
prerender() {
536+
super.prerender();
537+
538+
if (!this.root.enabled) {
539+
return;
540+
}
541+
542+
this._shapesLookAtCamera();
543+
}
535544
}
536545

537546
export { ScaleGizmo };

src/extras/gizmo/transform-gizmo.js

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -252,13 +252,6 @@ class TransformGizmo extends Gizmo {
252252
constructor(camera, layer) {
253253
super(camera, layer);
254254

255-
this._app.on('prerender', () => {
256-
if (!this.root.enabled) {
257-
return;
258-
}
259-
this._drawGuideLines();
260-
});
261-
262255
this.on(Gizmo.EVENT_POINTERDOWN, (x, y, meshInstance) => {
263256
const shape = this._shapeMap.get(meshInstance);
264257
if (shape?.disabled) {
@@ -551,7 +544,7 @@ class TransformGizmo extends Gizmo {
551544
* @protected
552545
*/
553546
_createPlane(axis, isFacing, isLine) {
554-
const facingDir = tmpV1.copy(this.facing);
547+
const facingDir = tmpV1.copy(this.facingDir);
555548
const normal = tmpP1.normal.set(0, 0, 0);
556549

557550
if (isFacing) {
@@ -732,6 +725,19 @@ class TransformGizmo extends Gizmo {
732725
return !this._shapes[shapeAxis].disabled;
733726
}
734727

728+
/**
729+
* @override
730+
*/
731+
prerender() {
732+
super.prerender();
733+
734+
if (!this.root.enabled) {
735+
return;
736+
}
737+
738+
this._drawGuideLines();
739+
}
740+
735741
/**
736742
* @override
737743
*/

0 commit comments

Comments
 (0)