Skip to content

Commit 59c3a3e

Browse files
committed
fix: Model scaling issue
1 parent 407e90f commit 59c3a3e

File tree

1 file changed

+139
-24
lines changed

1 file changed

+139
-24
lines changed

packages/@dcl/inspector/src/lib/babylon/decentraland/gizmo-manager.ts

Lines changed: 139 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -158,41 +158,156 @@ export function createGizmoManager(context: SceneContext) {
158158
}
159159
}
160160

161-
function updateEntityTransform(entity: Entity, newTransform: TransformType) {
162-
const { position, scale, rotation, parent } = newTransform
163-
context.operations.updateValue(context.Transform, entity, {
164-
position: DclVector3.create(position.x, position.y, position.z),
165-
rotation: DclQuaternion.create(rotation.x, rotation.y, rotation.z, rotation.w),
166-
scale: DclVector3.create(scale.x, scale.y, scale.z),
167-
parent
168-
})
169-
}
170-
171161
/**
172162
* Updates the transform of all selected entities after a gizmo operation
173163
*
174-
* 1. Fixes rotation gizmo alignment based on the first selected entity's transform
175-
* 2. For each selected entity:
176-
* - Gets the original parent and resolves it to a valid entity or root node
177-
* - Temporarily sets the entity's parent to handle transform calculations
178-
* - Updates the entity's transform:
179-
* - If parent is root node: Uses world space transform with snapping
180-
* - Otherwise: Uses local space transform
181-
* - Preserves the original parent relationship
182-
* 3. Dispatches the transform updates to persist changes
164+
* This function correctly applies gizmo transformations while maintaining
165+
* proper parent-child relationships and preventing scale distortion.
183166
*/
184167
function updateTransform() {
185-
fixRotationGizmoAlignment(getTransform(getFirstEntity()))
168+
const firstEntity = getFirstEntity()
169+
if (!firstEntity) return
170+
171+
fixRotationGizmoAlignment(getTransform(firstEntity))
172+
173+
// Get the gizmo dummy node
174+
const dummyNode = getDummyNode()
175+
176+
// Store which gizmo is active
177+
const { positionGizmoEnabled, rotationGizmoEnabled, scaleGizmoEnabled } = gizmoManager
178+
179+
// First measure the initial offsets when position gizmo is active
180+
// This allows us to maintain relative positions of multiple selected entities
181+
const initialOffsets = new Map<Entity, Vector3>()
182+
const initialDummyPos = dummyNode.position.clone()
183+
184+
if (positionGizmoEnabled && selectedEntities.length > 1) {
185+
// Calculate relative offsets from the dummy node for each entity
186+
for (const entity of selectedEntities) {
187+
const entityWorldPos = new Vector3()
188+
const entityRot = new Quaternion()
189+
const entityScale = new Vector3()
190+
entity.computeWorldMatrix(true).decompose(entityScale, entityRot, entityWorldPos)
191+
192+
// Store the offset from dummy node center
193+
initialOffsets.set(entity.entityId, entityWorldPos.subtract(initialDummyPos))
194+
}
195+
}
196+
197+
// Process each selected entity
186198
for (const entity of selectedEntities) {
187199
const originalParent = getParent(entity)
188-
const parent = context.getEntityOrNull(originalParent ?? context.rootNode.entityId)
200+
const parentEntity = context.getEntityOrNull(originalParent ?? context.rootNode.entityId)
201+
if (!parentEntity) continue
202+
203+
// Get the entity's original transform from ECS
204+
const originalTransform = entity.ecsComponentValues.transform
205+
if (!originalTransform) continue
206+
207+
// Create new transform object with original values
208+
const newTransform = {
209+
position: DclVector3.create(
210+
originalTransform.position.x,
211+
originalTransform.position.y,
212+
originalTransform.position.z
213+
),
214+
rotation: DclQuaternion.create(
215+
originalTransform.rotation.x,
216+
originalTransform.rotation.y,
217+
originalTransform.rotation.z,
218+
originalTransform.rotation.w
219+
),
220+
scale: DclVector3.create(originalTransform.scale.x, originalTransform.scale.y, originalTransform.scale.z),
221+
parent: originalParent
222+
}
189223

190-
entity.setParent(parent)
191-
const transform = parent === context.rootNode ? computeWorldTransform(entity) : getTransform(entity)
224+
if (positionGizmoEnabled) {
225+
// Calculate the final world position including offsets if multiple entities are selected
226+
let finalWorldPos: Vector3
227+
228+
if (selectedEntities.length > 1 && initialOffsets.has(entity.entityId)) {
229+
// For multiple entities: position = dummyNode.position + entity's offset from dummy
230+
const offset = initialOffsets.get(entity.entityId)!
231+
finalWorldPos = dummyNode.position.add(offset)
232+
} else {
233+
// For single entity: use the entity's exact world position
234+
const entityWorldMatrix = entity.computeWorldMatrix(true)
235+
finalWorldPos = new Vector3()
236+
const entityWorldRot = new Quaternion()
237+
const entityWorldScale = new Vector3()
238+
entityWorldMatrix.decompose(entityWorldScale, entityWorldRot, finalWorldPos)
239+
}
240+
241+
// Calculate parent's world matrix
242+
const parentWorldMatrix = parentEntity.computeWorldMatrix(true)
243+
const invParentWorld = parentWorldMatrix.clone()
244+
invParentWorld.invert()
245+
246+
// Convert world position to local position in parent's space
247+
const localPos = Vector3.TransformCoordinates(finalWorldPos, invParentWorld)
248+
249+
// Apply snapping
250+
const snappedLocalPos = snapPosition(localPos)
251+
252+
// Update the position in the transform
253+
newTransform.position = DclVector3.create(snappedLocalPos.x, snappedLocalPos.y, snappedLocalPos.z)
254+
} else if (rotationGizmoEnabled) {
255+
// For rotation, we need a more accurate approach to handle parent-child rotations
256+
257+
// Get the current entity's world position and world rotation after gizmo operation
258+
const entityWorldMatrix = entity.computeWorldMatrix(true)
259+
const entityWorldRotation = new Quaternion()
260+
const entityWorldScale = new Vector3()
261+
const entityWorldPos = new Vector3()
262+
entityWorldMatrix.decompose(entityWorldScale, entityWorldRotation, entityWorldPos)
263+
264+
// Get parent's world matrix to calculate local rotation
265+
const parentWorldMatrix = parentEntity.computeWorldMatrix(true)
266+
const invParentMatrix = parentWorldMatrix.clone()
267+
invParentMatrix.invert()
268+
269+
// Calculate local rotation by transforming world rotation to local space
270+
const invParentRotation = new Quaternion()
271+
const parentScale = new Vector3()
272+
const parentPos = new Vector3()
273+
invParentMatrix.decompose(parentScale, invParentRotation, parentPos)
274+
275+
// To get local rotation, multiply world rotation by inverse parent rotation
276+
// This is equivalent to: worldRot = parentRot * localRot, so localRot = parentRot^-1 * worldRot
277+
const localRotation = invParentRotation.multiply(entityWorldRotation)
278+
279+
// Apply snapping and update
280+
const snappedLocalRot = snapRotation(localRotation)
281+
newTransform.rotation = DclQuaternion.create(
282+
snappedLocalRot.x,
283+
snappedLocalRot.y,
284+
snappedLocalRot.z,
285+
snappedLocalRot.w
286+
)
287+
} else if (scaleGizmoEnabled) {
288+
// Calculate dummy node world matrix
289+
const dummyWorldMatrix = dummyNode.computeWorldMatrix(true)
290+
291+
// Extract entity's current scale from the world matrix
292+
const worldScale = new Vector3()
293+
const worldRot = new Quaternion()
294+
const worldPos = new Vector3()
295+
dummyWorldMatrix.decompose(worldScale, worldRot, worldPos)
296+
297+
// Apply snapping and update
298+
const snappedScale = snapScale(worldScale)
299+
newTransform.scale = DclVector3.create(snappedScale.x, snappedScale.y, snappedScale.z)
300+
}
192301

193-
updateEntityTransform(entity.entityId, { ...transform, parent: originalParent })
302+
// Apply the transform update
303+
context.operations.updateValue(context.Transform, entity.entityId, newTransform)
194304
}
195305

306+
// Make sure the entities are reparented properly
307+
restoreParents()
308+
repositionGizmoOnCentroid()
309+
310+
// Dispatch all transform updates
196311
void context.operations.dispatch()
197312
}
198313

0 commit comments

Comments
 (0)