I wanted to make the player's head track with the camera. So first, I had to disconnect the animations from controlling the neck and overriding whatever rotations I was setting. Then I had to make the neck's rotation correspond to the rotation of the camera. Defining this behavior turned out to be quite difficult. Firstly, I could not merely match the rotation of the camera to the neck because they used different systems to determine rotation. The `ArcRotateCamera` we are using has a target (the player) and 3 values to determine its position: alpha, beta, and radius. The neck on the other hand just uses a quaternion. I also wanted different behavior based on whether the camera was behind the player or in front of the player. If the camera was behind the player, I wanted the head to point to where the camera was looking, if the camera was in front of the player I wanted the head to look at the camera. So first I needed a way to determine whether the camera was in front of the player or behind the player. After trial and error, I determined that this was essentially converting the rotation measurements of the camera and the player to something I could directly compare. The way I described this was: imagine 2 CD's on a CD tower rotating independently of each other (the flattening here was important). Now draw a dot on the edge of the top CD and color in half of the bottom CD. Essentially what I was trying to do was determine when the dot on the top CD was over the colored-in portion of the bottom CD. This is how I got the direction that the player was facing: ```ts let playerDirection = new Vector3(0, 0, -1); playerDirection = playerDirection.rotateByQuaternionAroundPointToRef(playerRotation, new Vector3(0, 0, 0), playerDirection); const playerDirectionInRadians = Math.atan2(playerDirection.x, playerDirection.z); let normalizedPlayerDirection = playerDirectionInRadians % (2 * Math.PI); if (normalizedPlayerDirection < 0) { normalizedPlayerDirection += 2 * Math.PI; } ``` The alpha value for the camera was basically where the camera was in the horizontal orbit around the player. Normalizing this value to make sure the angle was between 0 and 2\*Pi, correcting negative values, and then inverting it to reflect it over the axis made it match the player direction's format. ```ts const alpha = camera.alpha; let normalizedAlpha = (alpha + Math.PI / 2) % (2 * Math.PI); if (normalizedAlpha < 0) { normalizedAlpha += 2 * Math.PI; } normalizedAlpha = 2 * Math.PI - normalizedAlpha; ``` Then I just had to get the absolute value of the difference between `normalizedAlpha` and `normalizedPlayerDirection` (and if the value was larger than Pi, subtract from 2\*Pi to get the complementary angle, ensuring it represented the smallest angle between the two) and see if the value was greater or less than Pi/2. This would determine whether the camera was in front of or behind the player. Then based on this information, I would set the neck's X rotation value to the beta or -beta value of the camera (along with an offset and clamping the values so the player didn't look up or down too much). ```ts let correction = -1.1; if (!isInFrontOfPlayer) { beta = -beta; correction = 1.9; } if (beta < 0.7 && isInFrontOfPlayer) { beta = 0.7; } if (beta > -0.8 && !isInFrontOfPlayer) { beta = -0.8; } const finalRotation = new Vector3(beta + correction, 0, 0); ``` While I was originally setting the neck rotation directly as described above, people complained that the neck's movement was too fast when the camera moved from the front of the player to the back (or vice versa) since it would involve a big rotation change. So I ended up sacrificing some responsiveness of the head movement for smoothness, by using a 1s animation with a cubic easing function to move the neck whenever the camera moved. ```ts const finalQuaternion = Quaternion.FromEulerVector(finalRotation); const easingFunction = new CubicEase(); easingFunction.setEasingMode(EasingFunction.EASINGMODE_EASEINOUT); Animation.CreateAndStartAnimation( 'neckRotationAnimation', window.localPlayer.neck, 'rotationQuaternion', 60, 60, window.localPlayer.neck.rotationQuaternion, finalQuaternion, Animation.ANIMATIONLOOPMODE_CONSTANT, easingFunction, ); ``` 7.27.23