Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 87 additions & 26 deletions src/extras/gizmo/gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,27 @@ class Gizmo extends EventHandler {
*/
_renderUpdate = false;

/**
* Internal enabled state requested by the user.
*
* @private
*/
_enabled = true;

/**
* Internal id of the pointer interacting with the gizmo.
*
* @private
*/
_pointerId = -1;

/**
* Internal flag to track if the gizmo has been destroyed.
*
* @private
*/
_destroyed = false;

/**
* The graph nodes attached to the gizmo.
*
Expand Down Expand Up @@ -289,9 +310,9 @@ class Gizmo extends EventHandler {
* Creates a new Gizmo object.
*
* @param {CameraComponent} camera - The camera component.
* @param {Layer} layer - The render layer. This can be provided by the user or will be created
* and added to the scene and camera if not provided. Successive gizmos will share the same layer
* and will be removed from the camera and scene when the last gizmo is destroyed.
* @param {Layer} layer - The render layer. The layer is added to the camera, and gizmos can share
* the same layer. It is removed from the camera when the last gizmo using it on that camera is
* destroyed or moved to another camera.
* @param {string} [name] - The name of the gizmo. Defaults to 'gizmo'.
* @example
* const gizmo = new Gizmo(camera, layer);
Expand Down Expand Up @@ -331,6 +352,7 @@ class Gizmo extends EventHandler {
* @type {boolean}
*/
set enabled(state) {
this._enabled = state;
const cameraDist = this.root.getLocalPosition().distance(this.camera.entity.getPosition());
const enabled = state ? this.nodes.length > 0 && cameraDist > DIST_EPSILON : false;
if (enabled !== this.root.enabled) {
Expand Down Expand Up @@ -373,11 +395,20 @@ class Gizmo extends EventHandler {
if (this._layer === layer) {
return;
}
this._camera.layers = this._camera.layers.filter(id => id !== this._layer.id);
this._releaseCameraLayer();
this._layer = layer;
this._camera.layers = this._camera.layers.concat(this._layer.id);

this.enabled = true;
// move the gizmo meshes to the new layer
const ids = [layer.id];
this.root.forEach((node) => {
const { render } = /** @type {Entity} */ (node);
if (render) {
render.layers = ids;
}
});

this.enabled = this._enabled;
}

/**
Expand All @@ -398,11 +429,11 @@ class Gizmo extends EventHandler {
if (this._camera === camera) {
return;
}
this._camera.layers = this._camera.layers.filter(id => id !== this._layer.id);
this._releaseCameraLayer();
this._camera = camera;
this._camera.layers = this._camera.layers.concat(this._layer.id);

this.enabled = true;
this.enabled = this._enabled;
}

/**
Expand Down Expand Up @@ -486,6 +517,12 @@ class Gizmo extends EventHandler {
if (!this.mouseButtons[e.button]) {
return;
}

// ignore other pointers while one is pressed
const { canvas } = this._device;
if (e.pointerId !== this._pointerId && canvas.hasPointerCapture(this._pointerId)) {
return;
}
const selection = this._getSelection(e.offsetX, e.offsetY);
if (selection[0]) {
if (this.preventDefault) {
Expand All @@ -495,7 +532,7 @@ class Gizmo extends EventHandler {
}

// capture the pointer during drag
const { canvas } = this._device;
this._pointerId = e.pointerId;
canvas.setPointerCapture(e.pointerId);

this.fire(Gizmo.EVENT_POINTERDOWN, e.offsetX, e.offsetY, selection[0]);
Expand All @@ -509,6 +546,9 @@ class Gizmo extends EventHandler {
if (!this.enabled || document.pointerLockElement) {
return;
}
if (e.pointerId !== this._pointerId && this._device.canvas.hasPointerCapture(this._pointerId)) {
return;
}
const selection = this._getSelection(e.offsetX, e.offsetY);
if (selection[0]) {
if (this.preventDefault) {
Expand All @@ -524,7 +564,7 @@ class Gizmo extends EventHandler {
* @private
*/
_onPointerUp(e) {
if (!this.mouseButtons[e.button]) {
if (!this.mouseButtons[e.button] || e.pointerId !== this._pointerId) {
return;
}

Expand All @@ -544,6 +584,21 @@ class Gizmo extends EventHandler {
this.fire(Gizmo.EVENT_POINTERUP, e.offsetX, e.offsetY, selection[0]);
}

/**
* Removes a single reference to the gizmo layer from the camera, as other gizmos may share the
* layer with the same camera.
*
* @private
*/
_releaseCameraLayer() {
const ids = this._camera.layers.slice();
const index = ids.indexOf(this._layer.id);
if (index !== -1) {
ids.splice(index, 1);
}
this._camera.layers = ids;
}

/** @protected */
_updatePosition() {
position.set(0, 0, 0);
Expand Down Expand Up @@ -617,7 +672,10 @@ class Gizmo extends EventHandler {
const end = this._camera.screenToWorld(x, y, this._camera.farClip - this._camera.nearClip);
const dir = v.copy(end).sub(start).normalize();

const selection = [];
/** @type {MeshInstance[] | null} */
let best = null;
let bestDist = 0;
let bestPriority = 0;
for (let i = 0; i < this.intersectShapes.length; i++) {
const shape = this.intersectShapes[i];
if (shape.disabled || !shape.entity.enabled) {
Expand All @@ -638,27 +696,22 @@ class Gizmo extends EventHandler {

for (let k = 0; k < tris.length; k++) {
if (tris[k].intersectsRay(ray, hit)) {
selection.push({
dist: triWTM.transformPoint(hit).sub(start).length(),
meshInstances: shape.meshInstances,
priority: priority
});
const dist = triWTM.transformPoint(hit).sub(start).length();

// priority decides between prioritized hits, otherwise distance
const wins = !best || (bestPriority !== 0 && priority !== 0 ?
priority > bestPriority : dist < bestDist);
if (wins) {
best = shape.meshInstances;
bestDist = dist;
bestPriority = priority;
}
}
}
}
}

if (selection.length) {
selection.sort((s0, s1) => {
if (s0.priority !== 0 && s1.priority !== 0) {
return s1.priority - s0.priority;
}
return s0.dist - s1.dist;
});
return selection[0].meshInstances;
}

return [];
return best ?? [];
}

/**
Expand Down Expand Up @@ -747,8 +800,16 @@ class Gizmo extends EventHandler {
* gizmo.destroy();
*/
destroy() {
// destroy can be called again, e.g. by the app destroy event after the user
if (this._destroyed) {
return;
}
this._destroyed = true;

this.detach();

this._releaseCameraLayer();

this._device.canvas.removeEventListener('pointerdown', this._onPointerDown);
this._device.canvas.removeEventListener('pointermove', this._onPointerMove);
this._device.canvas.removeEventListener('pointerup', this._onPointerUp);
Expand Down
27 changes: 27 additions & 0 deletions src/extras/gizmo/rotate-gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,33 @@ class RotateGizmo extends TransformGizmo {
return this._guideAngleLines[0].thickness;
}

/**
* Sets the gizmo render layer.
*
* @type {Layer}
*/
set layer(layer) {
super.layer = layer;

// the guide lines are not children of the gizmo root
const ids = [layer.id];
for (let i = 0; i < this._guideAngleLines.length; i++) {
const { render } = this._guideAngleLines[i].entity;
if (render) {
render.layers = ids;
}
}
}

/**
* Gets the gizmo render layer.
*
* @type {Layer}
*/
get layer() {
return super.layer;
}

/**
* @type {boolean}
* @deprecated Use {@link rotationMode} instead.
Expand Down
2 changes: 1 addition & 1 deletion src/extras/gizmo/scale-gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ class ScaleGizmo extends TransformGizmo {
// project to diagonal line
v1.set(1, 1, 1);
v1[axis] = 0;
point.copy(v1.mulScalar(v1.dot(point)));
point.copy(v1.mulScalar(v1.dot(point) / v1.lengthSq()));
point[axis] = 0;
}

Expand Down
15 changes: 9 additions & 6 deletions src/extras/gizmo/shape/arc-shape.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,6 @@ class ArcShape extends Shape {
this._createTorusMesh(360)
]);
this.show('sector');

// update transform
this._update();
}

/**
Expand Down Expand Up @@ -184,9 +181,15 @@ class ArcShape extends Shape {
this._triDataCache[0].fromGeometry(this._createTorusGeometry(this._sectorAngle));
this._triDataCache[1].fromGeometry(this._createTorusGeometry(360));

// render
this.meshInstances[0].mesh = this._createTorusMesh(this._sectorAngle);
this.meshInstances[1].mesh = this._createTorusMesh(360);
// render, destroying the replaced meshes
const angles = [this._sectorAngle, 360];
for (let i = 0; i < angles.length; i++) {
const mesh = this.meshInstances[i].mesh;
this.meshInstances[i].mesh = this._createTorusMesh(angles[i]);
if (mesh && mesh.refCount < 1) {
mesh.destroy();
}
}
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/extras/gizmo/transform-gizmo.js
Original file line number Diff line number Diff line change
Expand Up @@ -768,7 +768,7 @@ class TransformGizmo extends Gizmo {

// update shapes
for (const name in this._shapes) {
this._shapes[name].hover(!!this._hoverAxis);
this._shapes[name].hover(this._hovering.has(/** @type {GizmoAxis} */ (name)));
}
}

Expand Down
Loading