Skip to content

Commit cee4a98

Browse files
committed
fix: Scale Gizmo
1 parent 6ba797c commit cee4a98

1 file changed

Lines changed: 66 additions & 54 deletions

File tree

  • packages/@dcl/inspector/src/lib/babylon/decentraland/gizmos

packages/@dcl/inspector/src/lib/babylon/decentraland/gizmos/ScaleGizmo.ts

Lines changed: 66 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,16 @@ import { Vector3, TransformNode, GizmoManager, Quaternion, Matrix } from '@babyl
22
import { Entity } from '@dcl/ecs'
33
import { EcsEntity } from '../EcsEntity'
44
import { IGizmoTransformer } from './types'
5-
import { TransformUtils } from './utils'
65

76
export class ScaleGizmo implements IGizmoTransformer {
87
private initialOffsets = new Map<Entity, Vector3>()
98
private initialScales = new Map<Entity, Vector3>()
109
private initialRotations = new Map<Entity, Quaternion>()
10+
private initialPositions = new Map<Entity, Vector3>()
11+
private pivotPosition: Vector3 | null = null
1112
private initialGizmoScale: Vector3 | null = null
1213
private changeHandlers: (() => void)[] = []
14+
private isDragging = false
1315

1416
constructor(private gizmoManager: GizmoManager) {}
1517

@@ -25,38 +27,52 @@ export class ScaleGizmo implements IGizmoTransformer {
2527
this.initialOffsets.clear()
2628
this.initialScales.clear()
2729
this.initialRotations.clear()
30+
this.initialPositions.clear()
2831
this.initialGizmoScale = null
32+
this.pivotPosition = null
33+
this.isDragging = false
2934
}
3035

3136
onDragStart(entities: EcsEntity[], gizmoNode: TransformNode): void {
32-
const centroid = gizmoNode.position.clone()
37+
if (this.isDragging) return
38+
39+
this.isDragging = true
40+
41+
// Calculate pivot position (centroid of all selected entities)
42+
this.pivotPosition = new Vector3()
43+
for (const entity of entities) {
44+
const worldPosition = entity.getAbsolutePosition()
45+
this.pivotPosition.addInPlace(worldPosition)
46+
}
47+
this.pivotPosition.scaleInPlace(1 / entities.length)
48+
49+
// Store initial gizmo scale
50+
this.initialGizmoScale = gizmoNode.scaling.clone()
51+
52+
// Store initial state for all entities
3353
this.initialOffsets.clear()
3454
this.initialScales.clear()
3555
this.initialRotations.clear()
36-
this.initialGizmoScale = gizmoNode.scaling.clone()
56+
this.initialPositions.clear()
3757

3858
for (const entity of entities) {
3959
const worldPosition = entity.getAbsolutePosition()
40-
const offset = worldPosition.subtract(centroid)
41-
this.initialOffsets.set(entity.entityId, offset)
4260

43-
const worldRotation = TransformUtils.getWorldRotation(entity)
44-
this.initialRotations.set(entity.entityId, worldRotation)
61+
// Store initial transforms
62+
this.initialPositions.set(entity.entityId, entity.position.clone())
63+
this.initialScales.set(entity.entityId, entity.scaling.clone())
64+
this.initialRotations.set(entity.entityId, entity.rotationQuaternion?.clone() || Quaternion.Identity())
4565

46-
const parent = entity.parent instanceof TransformNode ? entity.parent : null
47-
const parentScale = TransformUtils.getParentWorldScale(parent)
48-
const worldScale = new Vector3(
49-
entity.scaling.x * parentScale.x,
50-
entity.scaling.y * parentScale.y,
51-
entity.scaling.z * parentScale.z
52-
)
53-
this.initialScales.set(entity.entityId, worldScale)
66+
// Store offset from pivot (for proportional scaling)
67+
const offset = worldPosition.subtract(this.pivotPosition)
68+
this.initialOffsets.set(entity.entityId, offset)
5469
}
5570
}
5671

5772
update(entities: EcsEntity[], gizmoNode: TransformNode): void {
58-
if (!this.initialGizmoScale) return
73+
if (!this.isDragging || !this.initialGizmoScale || !this.pivotPosition) return
5974

75+
// Calculate scale change from gizmo
6076
const scaleChange = new Vector3(
6177
gizmoNode.scaling.x / this.initialGizmoScale.x,
6278
gizmoNode.scaling.y / this.initialGizmoScale.y,
@@ -67,11 +83,15 @@ export class ScaleGizmo implements IGizmoTransformer {
6783
const offset = this.initialOffsets.get(entity.entityId)
6884
const initialScale = this.initialScales.get(entity.entityId)
6985
const initialRotation = this.initialRotations.get(entity.entityId)
70-
if (!offset || !initialScale || !initialRotation) continue
86+
const initialPosition = this.initialPositions.get(entity.entityId)
87+
88+
if (!offset || !initialScale || !initialRotation || !initialPosition) continue
7189

90+
// Scale the offset proportionally (like Blender's proportional scaling)
7291
const scaledOffset = new Vector3(offset.x * scaleChange.x, offset.y * scaleChange.y, offset.z * scaleChange.z)
73-
const newWorldPosition = gizmoNode.position.add(scaledOffset)
92+
const newWorldPosition = this.pivotPosition.add(scaledOffset)
7493

94+
// Scale the entity's scale
7595
const newWorldScale = new Vector3(
7696
initialScale.x * scaleChange.x,
7797
initialScale.y * scaleChange.y,
@@ -81,50 +101,35 @@ export class ScaleGizmo implements IGizmoTransformer {
81101
const parent = entity.parent instanceof TransformNode ? entity.parent : null
82102

83103
if (parent) {
104+
// For child entities, convert world transforms to local space
84105
const parentWorldMatrix = parent.getWorldMatrix()
85-
const parentScale = TransformUtils.getParentWorldScale(parent)
86-
const parentRotation =
87-
parent.rotationQuaternion ||
88-
Quaternion.FromRotationMatrix(
89-
Matrix.FromValues(
90-
parentWorldMatrix.m[0],
91-
parentWorldMatrix.m[1],
92-
parentWorldMatrix.m[2],
93-
0,
94-
parentWorldMatrix.m[4],
95-
parentWorldMatrix.m[5],
96-
parentWorldMatrix.m[6],
97-
0,
98-
parentWorldMatrix.m[8],
99-
parentWorldMatrix.m[9],
100-
parentWorldMatrix.m[10],
101-
0,
102-
0,
103-
0,
104-
0,
105-
1
106-
)
107-
)
108-
109-
const localPosition = TransformUtils.convertToLocalPosition(newWorldPosition, parent)
110-
entity.position.copyFrom(localPosition)
106+
const parentWorldMatrixInverse = parentWorldMatrix.clone().invert()
107+
const parentWorldRotation = parent.rotationQuaternion || Quaternion.FromRotationMatrix(parentWorldMatrix)
111108

112-
entity.scaling.set(
113-
newWorldScale.x / parentScale.x,
114-
newWorldScale.y / parentScale.y,
115-
newWorldScale.z / parentScale.z
116-
)
109+
// Convert world position to local space
110+
const localPosition = Vector3.TransformCoordinates(newWorldPosition, parentWorldMatrixInverse)
117111

118-
const scaleCompensationMatrix = Matrix.Scaling(1 / parentScale.x, 1 / parentScale.y, 1 / parentScale.z)
119-
const compensatedRotation = initialRotation.multiply(Quaternion.FromRotationMatrix(scaleCompensationMatrix))
120-
const localRotation = parentRotation.invert().multiply(compensatedRotation)
112+
// Convert world rotation to local space
113+
const localRotation = parentWorldRotation.invert().multiply(initialRotation)
121114

115+
// Apply scale directly to the child without considering parent's scale
116+
// This maintains the local scale as intended by the user
117+
const localScale = new Vector3(
118+
initialScale.x * scaleChange.x,
119+
initialScale.y * scaleChange.y,
120+
initialScale.z * scaleChange.z
121+
)
122+
123+
// Apply transforms
124+
entity.position.copyFrom(localPosition)
125+
entity.scaling.copyFrom(localScale)
122126
if (!entity.rotationQuaternion) {
123127
entity.rotationQuaternion = new Quaternion()
124128
}
125129
entity.rotationQuaternion.copyFrom(localRotation)
126130
entity.rotationQuaternion.normalize()
127131
} else {
132+
// For entities without parent, apply world transforms directly
128133
entity.position.copyFrom(newWorldPosition)
129134
entity.scaling.copyFrom(newWorldScale)
130135
if (!entity.rotationQuaternion) {
@@ -134,15 +139,22 @@ export class ScaleGizmo implements IGizmoTransformer {
134139
entity.rotationQuaternion.normalize()
135140
}
136141

137-
this.changeHandlers.forEach((handler) => handler())
142+
// Force update world matrix
143+
entity.computeWorldMatrix(true)
138144
}
145+
146+
// Notify change handlers
147+
this.changeHandlers.forEach((handler) => handler())
139148
}
140149

141150
onDragEnd(): void {
151+
this.isDragging = false
152+
this.initialGizmoScale = null
153+
this.pivotPosition = null
142154
this.initialOffsets.clear()
143155
this.initialScales.clear()
144156
this.initialRotations.clear()
145-
this.initialGizmoScale = null
157+
this.initialPositions.clear()
146158
}
147159

148160
onChange(callback: () => void): () => void {

0 commit comments

Comments
 (0)