diff --git a/editor/src/editor/layout/graph.tsx b/editor/src/editor/layout/graph.tsx index 248ddf862..72849b65c 100644 --- a/editor/src/editor/layout/graph.tsx +++ b/editor/src/editor/layout/graph.tsx @@ -14,7 +14,7 @@ import { IoCheckmark, IoSparklesSharp } from "react-icons/io5"; import { SiAdobeindesign, SiBabylondotjs } from "react-icons/si"; import { AdvancedDynamicTexture } from "babylonjs-gui"; -import { BaseTexture, Node, Scene, Sound, Tools, IParticleSystem, ParticleSystem } from "babylonjs"; +import { BaseTexture, Node, Scene, Sound, Tools, TransformNode, IParticleSystem, ParticleSystem } from "babylonjs"; import { Editor } from "../main"; @@ -399,7 +399,7 @@ export class EditorGraph extends Component instance.rotation.copyFrom(object.rotation); instance.scaling.copyFrom(object.scaling); instance.rotationQuaternion = object.rotationQuaternion?.clone() ?? null; - instance.parent = object.parent; + this._setParentPreservingWorldTransform(instance, object.parent); const collisionMesh = getCollisionMeshFor(instance.sourceMesh); collisionMesh?.updateInstances(instance.sourceMesh); @@ -427,7 +427,7 @@ export class EditorGraph extends Component node.uniqueId = UniqueNumber.Get(); if (parent && isNode(node)) { - node.parent = parent; + this._setParentPreservingWorldTransform(node, parent); } if (isAbstractMesh(node)) { @@ -899,4 +899,79 @@ export class EditorGraph extends Component this.refresh(); } + + private _setParentPreservingWorldTransform(node: Node, newParent: Node | null): void { + // TransformNodes (including Meshes) support full world transform preservation + if (node instanceof TransformNode) { + // Store the current world transform + const worldPosition = node.getAbsolutePosition(); + const worldRotation = node.rotationQuaternion || node.rotation.toQuaternion(); + const worldScaling = node.absoluteScaling.clone(); + + // Set the new parent + node.parent = newParent; + + // Restore the world transform + node.position.copyFrom(worldPosition); + + // Compute the local rotation based on the parent's rotation + let localRotation = worldRotation; + if (newParent instanceof TransformNode) { + const parentRotation = newParent.absoluteRotationQuaternion; + localRotation = parentRotation.conjugate().multiply(worldRotation); + } + + if (node.rotationQuaternion) { + node.rotationQuaternion.copyFrom(localRotation); + } else { + node.rotation.copyFrom(localRotation.toEulerAngles()); + } + + node.scaling.copyFrom(worldScaling); + return; + } + + // Cameras and Lights have position and rotation but not the full transform methods + if (isCamera(node) || isLight(node)) { + // Store current world position and rotation + const worldPosition = (node as any).position.clone(); + const worldRotation = (node as any).rotationQuaternion || (node as any).rotation.toQuaternion(); + + // Set the new parent + node.parent = newParent; + + // For cameras and lights, we need to compute local position manually + if (newParent instanceof TransformNode) { + // Compute local position from world position + const localPosition = worldPosition.subtract(newParent.getAbsolutePosition()); + const parentRotationInverse = newParent.absoluteRotationQuaternion.conjugate(); + localPosition.applyRotationQuaternionInPlace(parentRotationInverse); + localPosition.divideInPlace(newParent.absoluteScaling); + (node as any).position.copyFrom(localPosition); + + // Compute local rotation + const parentRotation = newParent.absoluteRotationQuaternion; + const localRotation = parentRotation.conjugate().multiply(worldRotation); + + if ((node as any).rotationQuaternion) { + (node as any).rotationQuaternion.copyFrom(localRotation); + } else { + (node as any).rotation.copyFrom(localRotation.toEulerAngles()); + } + } else { + // No parent, world position/rotation equals local position/rotation + (node as any).position.copyFrom(worldPosition); + + if ((node as any).rotationQuaternion) { + (node as any).rotationQuaternion.copyFrom(worldRotation); + } else { + (node as any).rotation.copyFrom(worldRotation.toEulerAngles()); + } + } + return; + } + + // For other node types, just set the parent directly + node.parent = newParent; + } }