Repository: playcanvas/engine (js)

Issue:
Add method for Node
Now there are 2 methods available, setPosition and setRotation, I suggest adding 1 more method setPositionAndRotation.

public setPositionAndRotation(position: Readonly<pc.Vec3>, rotation: Readonly<pc.Quat>): void;

* This will further optimize position and rotation manipulations

Pull request description:
GraphNode setPositionAndRotation
Fixes #6317

Adds a method to graph node class for apply both position and rotation together.

### Performance Testing

**Average of 5**
**1e6 Iterations**
**Random Positions [-5, 5]**
**Random Rotations [0, 360]**

| setPos + setRot | setTRS | setPosRot |
| -------- | ------- | ------- |
| 178ms  | 18.6ms   | 105ms |

Using `entity.getWorldTransform().setTRS` directly is the fastest however the local positions and rotations are not updated and so neither is the local transform.  


Patch:
diff --git a/src/scene/graph-node.js b/src/scene/graph-node.js
index dff45784a..65e53cd1b 100644
--- a/src/scene/graph-node.js
+++ b/src/scene/graph-node.js
@@ -1179,6 +1179,32 @@ class GraphNode extends EventHandler {
             this._dirtifyLocal();
     }
 
+    /**
+     * Sets the world-space position and rotation of the specified graph node. This is faster than
+     * setting the position and rotation independently.
+     *
+     * @param {Vec3} position - The world-space position to set.
+     * @param {Quat} rotation - The world-space rotation to set.
+     * @example
+     * const position = new pc.Vec3(0, 10, 0);
+     * const rotation = new pc.Quat().setFromEulerAngles(0, 90, 0);
+     * this.entity.setPositionAndRotation(position, rotation);
+     */
+    setPositionAndRotation(position, rotation) {
+        if (this._parent === null) {
+            this.localPosition.copy(position);
+            this.localRotation.copy(rotation);
+        } else {
+            const parentWtm = this._parent.getWorldTransform();
+            invParentWtm.copy(parentWtm).invert();
+            invParentWtm.transformPoint(position, this.localPosition);
+            this.localRotation.setFromMat4(invParentWtm).mul(rotation);
+        }
+
+        if (!this._dirtyLocal)
+            this._dirtifyLocal();
+    }
+
     /**
      * Sets the world-space rotation of the specified graph node using euler angles. Eulers are
      * interpreted in XYZ order. Eulers must be specified in degrees. This function has two valid


Test patch:
diff --git a/test/scene/graph-node.test.mjs b/test/scene/graph-node.test.mjs
index 74a87b0f3..a534ba447 100644
--- a/test/scene/graph-node.test.mjs
+++ b/test/scene/graph-node.test.mjs
@@ -851,4 +851,48 @@ describe('GraphNode', function () {
 
     });
 
+    describe('#setPositionAndRotation()', function () {
+        it('setPositionAndRotation is the same as setPosition and setRotation', function () {
+            const root = new GraphNode('root');
+            const child = new GraphNode('child');
+            root.addChild(child);
+            root.setPosition(10, 20, 30);
+
+            root.setLocalScale(0.5, 0.5, 0.5);
+
+            const pos = new Vec3(1, 2, 3);
+            const rot = new Quat().setFromEulerAngles(10, 20, 30);
+            child.setPosition(pos);
+            child.setRotation(rot);
+
+            const pos1 = child.getPosition().clone();
+            const rot1 = child.getRotation().clone();
+            const localPos1 = child.getLocalPosition().clone();
+            const localRot1 = child.getLocalRotation().clone();
+
+            child.setPositionAndRotation(pos, rot);
+
+            const pos2 = child.getPosition();
+            expect(pos2.x).to.be.closeTo(pos1.x, 0.00001);
+            expect(pos2.y).to.be.closeTo(pos1.y, 0.00001);
+            expect(pos2.z).to.be.closeTo(pos1.z, 0.00001);
+
+            const rot2 = child.getRotation();
+            expect(rot2.x).to.be.closeTo(rot1.x, 0.00001);
+            expect(rot2.y).to.be.closeTo(rot1.y, 0.00001);
+            expect(rot2.z).to.be.closeTo(rot1.z, 0.00001);
+            expect(rot2.w).to.be.closeTo(rot1.w, 0.00001);
+
+            const localPos2 = child.getLocalPosition();
+            expect(localPos2.x).to.be.closeTo(localPos1.x, 0.00001);
+            expect(localPos2.y).to.be.closeTo(localPos1.y, 0.00001);
+            expect(localPos2.z).to.be.closeTo(localPos1.z, 0.00001);
+
+            const localRot2 = child.getLocalRotation();
+            expect(localRot2.x).to.be.closeTo(localRot1.x, 0.00001);
+            expect(localRot2.y).to.be.closeTo(localRot1.y, 0.00001);
+            expect(localRot2.z).to.be.closeTo(localRot1.z, 0.00001);
+            expect(localRot2.w).to.be.closeTo(localRot1.w, 0.00001);
+        });
+    });
 });