Skip to content

Commit

Permalink
center gizmo when multiple entities are selected
Browse files Browse the repository at this point in the history
  • Loading branch information
nicoecheza committed Nov 12, 2024
1 parent 5502b7c commit 46b6606
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 17 deletions.
2 changes: 1 addition & 1 deletion packages/@dcl/inspector/src/components/Tree/Tree.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ export function Tree<T>() {
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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand Down Expand Up @@ -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()
Expand All @@ -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()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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
Expand All @@ -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 }>()
Expand Down Expand Up @@ -180,6 +199,53 @@ export function createGizmoManager(context: SceneContext) {
}
}

// Map to store the original parent of each entity
const originalParents = new Map<Entity, TransformNode | null>();

// 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)
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 })
}

Expand Down

0 comments on commit 46b6606

Please sign in to comment.