From 2c79a0a8693392c767346223198a3a32e850b660 Mon Sep 17 00:00:00 2001 From: Nicolas Echezarreta Date: Tue, 5 Nov 2024 17:15:03 -0300 Subject: [PATCH] center gizmo when multiple entities are selected --- .../inspector/src/components/Tree/Tree.tsx | 2 +- .../editorComponents/selection.ts | 12 ++- .../lib/babylon/decentraland/gizmo-manager.ts | 93 +++++++++++++++++-- .../sdk/operations/update-selected-entity.ts | 7 +- 4 files changed, 97 insertions(+), 17 deletions(-) diff --git a/packages/@dcl/inspector/src/components/Tree/Tree.tsx b/packages/@dcl/inspector/src/components/Tree/Tree.tsx index 31125d285..f64a91e95 100644 --- a/packages/@dcl/inspector/src/components/Tree/Tree.tsx +++ b/packages/@dcl/inspector/src/components/Tree/Tree.tsx @@ -152,7 +152,7 @@ export function Tree() { const quitInsertMode = () => setInsertMode(false) const handleSelect = (event: React.MouseEvent) => { - if (event.type === ClickType.CONTEXT_MENU && event.ctrlKey) { + if (event.type === ClickType.CONTEXT_MENU && (event.ctrlKey || event.shiftKey)) { onSelect(value, true) } else if (event.type === ClickType.CLICK) { onSelect(value) diff --git a/packages/@dcl/inspector/src/lib/babylon/decentraland/editorComponents/selection.ts b/packages/@dcl/inspector/src/lib/babylon/decentraland/editorComponents/selection.ts index 9d4f9b77e..41a395ebc 100644 --- a/packages/@dcl/inspector/src/lib/babylon/decentraland/editorComponents/selection.ts +++ b/packages/@dcl/inspector/src/lib/babylon/decentraland/editorComponents/selection.ts @@ -1,4 +1,4 @@ -import { AbstractMesh, Color3 } from '@babylonjs/core' +import { AbstractMesh, Color3, Gizmo } from '@babylonjs/core' import { ComponentType } from '@dcl/ecs' import { CoreComponents } from '../../../sdk/components' import { EcsEntity } from '../EcsEntity' @@ -40,12 +40,11 @@ export const toggleSelection = (entity: EcsEntity, value: boolean) => { export const updateGizmoManager = (entity: EcsEntity, value: { gizmo: number } | null) => { const context = entity.context.deref()! - let processedSomeEntity = false const Transform = context.engine.getComponent(CoreComponents.TRANSFORM) + const selectedEntities = Array.from(context.engine.getEntitiesWith(context.editorComponents.Selection)) - for (const [_entity] of context.engine.getEntitiesWith(context.editorComponents.Selection)) { - processedSomeEntity = true + for (const [_entity] of selectedEntities) { if (entity.entityId === _entity && Transform.has(_entity)) { context.gizmos.setEntity(entity) const types = context.gizmos.getGizmoTypes() @@ -55,7 +54,10 @@ export const updateGizmoManager = (entity: EcsEntity, value: { gizmo: number } | } } - if (!processedSomeEntity) { + if (selectedEntities.length === 0) { context.gizmos.unsetEntity() + } else { + // TODO: this will also execute when only one entity is selected, it works but it's not optimal... + context.gizmos.repositionGizmoOnCentroid() } } diff --git a/packages/@dcl/inspector/src/lib/babylon/decentraland/gizmo-manager.ts b/packages/@dcl/inspector/src/lib/babylon/decentraland/gizmo-manager.ts index 209551571..1fa7df108 100644 --- a/packages/@dcl/inspector/src/lib/babylon/decentraland/gizmo-manager.ts +++ b/packages/@dcl/inspector/src/lib/babylon/decentraland/gizmo-manager.ts @@ -6,7 +6,8 @@ import { Node, Vector3, PointerDragBehavior, - AbstractMesh + AbstractMesh, + TransformNode } from '@babylonjs/core' import { Entity, TransformType } from '@dcl/ecs' import { Vector3 as DclVector3, Quaternion as DclQuaternion } from '@dcl/ecs-math' @@ -17,6 +18,8 @@ import { SceneContext } from './SceneContext' import { PatchedGizmoManager } from './gizmo-patch' import { ROOT } from '../../sdk/tree' +const GIZMO_DUMMY_NODE = "GIZMO_DUMMY_NODE" + interface GizmoAxis { xGizmo: IAxisDragGizmo yGizmo: IAxisDragGizmo @@ -39,6 +42,22 @@ function areQuaternionsEqual(a: DclQuaternion, b: DclQuaternion) { return a.x === b.x && a.y === b.y && a.z === b.z && a.w === b.w } +function calculateCenter(positions: Vector3[]): Vector3 { + if (positions.length === 0) throw new Error("No positions provided to calculate center") + + const sum = positions.reduce( + (acc, pos) => { + acc.x += pos.x + acc.y += pos.y + acc.z += pos.z + return acc + }, + new Vector3(0, 0, 0) + ) + + return sum.scale(1 / positions.length) +} + export function createGizmoManager(context: SceneContext) { // events const events = mitt<{ change: void }>() @@ -180,6 +199,53 @@ export function createGizmoManager(context: SceneContext) { } } + // Map to store the original parent of each entity + const originalParents = new Map(); + + // Check if a transform node for the gizmo already exists, or create one + function getDummyNode(): TransformNode { + let dummyNode = context.scene.getTransformNodeByName(GIZMO_DUMMY_NODE) as TransformNode + if (!dummyNode) dummyNode = new TransformNode(GIZMO_DUMMY_NODE, context.scene) as TransformNode + return dummyNode + } + + function repositionGizmoOnCentroid() { + const selectedEntities = getSelectedEntities().map((entityId) => context.getEntityOrNull(entityId)!) + const positions = selectedEntities.map((entity) => { + const { x, y, z } = getTransform(entity).position + return new Vector3(x, y, z) + }) + const centroidPosition = calculateCenter(positions) + const dummyNode = getDummyNode() + + // Set the dummy node position on centroid. This should be the first thing to do on the dummy node + // so everything aligns to the right position afterwards. + dummyNode.position = centroidPosition + + // Store the original parents and set the dummy node as parent for each selected entity + selectedEntities.forEach((entity) => { + const parent = entity.parent as TransformNode | null + originalParents.set(entity.entityId, parent) + entity.setParent(dummyNode) + }) + + // Attach the gizmo to the dummy node + gizmoManager.attachToNode(dummyNode) + } + + function restoreOriginalParents() { + originalParents.forEach((parent, entity) => { + const ecsEntity = context.getEntityOrNull(entity)! + ecsEntity.setParent(parent) + }) + + // Clear the stored parents as they're now restored + originalParents.clear() + + // Detach the gizmo from the dummy node if needed + gizmoManager.attachToNode(null) + } + gizmoManager.gizmos.scaleGizmo?.onDragStartObservable.add(initTransform) gizmoManager.gizmos.positionGizmo?.onDragStartObservable.add(initTransform) gizmoManager.gizmos.rotationGizmo?.onDragStartObservable.add(initTransform) @@ -313,20 +379,29 @@ export function createGizmoManager(context: SceneContext) { if ( entity === lastEntity || !isEnabled || - areMultipleEntitiesSelected() || entity?.isHidden() || entity?.isLocked() || entity?.getRoot() !== ROOT ) { return } - gizmoManager.attachToNode(entity) - lastEntity = entity - // fix gizmo rotation/position if necessary - const transform = getTransform() - fixRotationGizmoAlignment(transform) - fixPositionGizmoAlignment(transform) - events.emit('change') + restoreOriginalParents() + if (areMultipleEntitiesSelected()) { + repositionGizmoOnCentroid() + return + } else { + gizmoManager.attachToNode(entity) + lastEntity = entity + // fix gizmo rotation/position if necessary + const transform = getTransform() + fixRotationGizmoAlignment(transform) + fixPositionGizmoAlignment(transform) + events.emit('change') + } + }, + repositionGizmoOnCentroid() { + restoreOriginalParents() + return repositionGizmoOnCentroid() }, getEntity() { return lastEntity diff --git a/packages/@dcl/inspector/src/lib/sdk/operations/update-selected-entity.ts b/packages/@dcl/inspector/src/lib/sdk/operations/update-selected-entity.ts index 159f50d5f..460cde85c 100644 --- a/packages/@dcl/inspector/src/lib/sdk/operations/update-selected-entity.ts +++ b/packages/@dcl/inspector/src/lib/sdk/operations/update-selected-entity.ts @@ -49,8 +49,11 @@ export function updateSelectedEntity(engine: IEngine) { } } - // then select new entity - if (!Selection.has(entity) || deletedSelection) { + // allow deselecting from a list of selected entities... + if (multiple && Selection.has(entity)) { + Selection.deleteFrom(entity) + } else if (!Selection.has(entity) || deletedSelection) { + // then select new entity Selection.createOrReplace(entity, { gizmo }) }