diff --git a/packages/client/src/3d/Phantom.tsx b/packages/client/src/3d/Phantom.tsx index 74373da..4a7f11e 100644 --- a/packages/client/src/3d/Phantom.tsx +++ b/packages/client/src/3d/Phantom.tsx @@ -23,7 +23,7 @@ const Phantom: React.FC = ({ id }) => { state.position[1] + 0.25, state.position[2], ]) - ref.current?.rotation.fromArray(state.rotation) + ref.current?.quaternion.fromArray(state.rotation) }, (state) => state?.userTransforms?.[id], ) diff --git a/packages/client/src/3d/Player.tsx b/packages/client/src/3d/Player.tsx index 331fd2e..c6ad979 100644 --- a/packages/client/src/3d/Player.tsx +++ b/packages/client/src/3d/Player.tsx @@ -10,10 +10,12 @@ import { Mesh, CircleGeometry, } from 'three' +import { relativeAngle } from './lib/math' import WorldCollisions from './models/WorldCollisions' import useStore from '../store' import { MeshBasicMaterial } from 'three/src/materials/MeshBasicMaterial' +import { Quaternion } from 'three/src/math/Quaternion' const SPEED = 1 const HEIGHT = 1.5 @@ -22,10 +24,10 @@ const CIRCLE_SEGMENTS = 8 const InitialPosition = new Vector3(49.92, 3.15, 34.52) const keys: Record = { - KeyW: 'forward', - KeyS: 'backward', - KeyA: 'left', - KeyD: 'right', + KeyW: 'left', + KeyS: 'right', + KeyA: 'backward', + KeyD: 'forward', ArrowUp: 'forward', ArrowDown: 'backward', ArrowLeft: 'left', @@ -35,6 +37,20 @@ const keys: Record = { } const moveFieldByKey = (key: string) => keys[key] +const animations: Record = { + idle: 0, + turn_right: 1, + turn_left: 2, + walk_backwards: 3, + walk: 4, + walk_right: 5, + walk_left: 6, + run: 7, + run_right: 8, + run_left: 9, + jump: 10, +} + function FirstPersonCamera(props: JSX.IntrinsicElements['perspectiveCamera']) { const ref = useRef() const { setDefaultCamera } = useThree() @@ -73,6 +89,9 @@ const usePlayerControls = () => { return movement } +// define an axis, usually just up +const upVector = new Vector3(0, 1, 0) + // TODO Improve physics in player const Player = () => { const socket = useStore((state) => state.socket) @@ -84,8 +103,10 @@ const Player = () => { const collisionsRef = useRef>(null) const velocity = useRef(new Vector3()) const direction = useRef(new Vector3()) - const worldOrigin = useRef(new Vector3()) const currentPositionClone = useRef(new Vector3()) + const pCameraQuaternion = useRef(new Quaternion()) + const worldOrigin = useRef(new Vector3()) + const speed = useRef(SPEED) const bottomRaycaster = useRef( new Raycaster(new Vector3(), new Vector3(0, -1, 0), 0, HEIGHT + 0.5), @@ -113,22 +134,53 @@ const Player = () => { useEffect(() => { const socketEmitTransformInterval = setInterval(() => { if (socket && groupRef.current && camera) { - const cameraRotation = camera.rotation.toArray() + const cameraRotation = camera.quaternion.toArray() + const [x, , z] = direction.current.toArray() + + let anim = animations.idle + + if (x === 0 && z === 0) { + const rotationAngle = relativeAngle( + upVector, + camera.quaternion, + pCameraQuaternion.current, + ) + + if (Math.abs(rotationAngle) > 10) { + anim = + rotationAngle > 1 ? animations.turn_right : (anim = animations.turn_left) + } else { + anim = animations.idle + } + } else if (x < 0) { + anim = animations.walk_backwards + } else if (x > 0 && z === 0) { + anim = speed.current > SPEED ? animations.run : animations.walk + } else if (x >= 0 && z > 0) { + anim = speed.current > SPEED ? animations.run_right : animations.walk_right + } else if (x >= 0 && z < 0) { + anim = speed.current > SPEED ? animations.run_left : animations.walk_left + } + + // get the signed difference in these angles socket.emit('transform', { position: [ groupRef.current?.position.x, groupRef.current?.position.y, groupRef.current?.position.z, ], - rotation: [cameraRotation[0], cameraRotation[1], cameraRotation[2]], + rotation: [...cameraRotation], + a: anim, }) + + pCameraQuaternion.current.copy(camera.quaternion) } - }, 50) + }, 1000) return () => { clearInterval(socketEmitTransformInterval) } - }, []) + }, [socket, groupRef.current, camera]) const moveForward = (distance: number) => { if (groupRef.current) { @@ -175,12 +227,13 @@ const Player = () => { direction.current.normalize() // Running - const speed = run ? 1.5 : 1 + speed.current = run && direction.current.x >= 0 ? 1.5 : 1 // Move if (forward || backward) - velocity.current.z -= direction.current.z * SPEED * delta * speed - if (left || right) velocity.current.x -= direction.current.x * SPEED * delta * speed + velocity.current.z -= direction.current.z * SPEED * delta * speed.current + if (left || right) + velocity.current.x -= direction.current.x * SPEED * delta * speed.current // Wall collisions const collisionCircleGeometry = collisionCircle.current.geometry as Geometry @@ -213,8 +266,8 @@ const Player = () => { } // Apply speed - moveForward(-velocity.current.x * speed) - moveRight(-velocity.current.z * speed) + moveForward(-velocity.current.x * speed.current) + moveRight(-velocity.current.z * speed.current) groupRef.current.position.y += velocity.current.y * delta } @@ -224,8 +277,8 @@ const Player = () => { }) return ( <> - - + + { + const num1 = rotation.x * 2 + const num2 = rotation.y * 2 + const num3 = rotation.z * 2 + const num4 = rotation.x * num1 + const num5 = rotation.y * num2 + const num6 = rotation.z * num3 + const num7 = rotation.x * num2 + const num8 = rotation.x * num3 + const num9 = rotation.y * num3 + const num10 = rotation.w * num1 + const num11 = rotation.w * num2 + const num12 = rotation.w * num3 + const v = new Vector3() + v.x = + (1.0 - (num5 + num6)) * point.x + (num7 - num12) * point.y + (num8 + num11) * point.z + v.y = + (num7 + num12) * point.x + (1.0 - (num4 + num6)) * point.y + (num9 - num10) * point.z + v.z = + (num8 - num11) * point.x + (num9 + num10) * point.y + (1.0 - (num4 + num5)) * point.z + return v +} + +export const repeat = (t: number, length: number): number => { + return t - Math.floor(t / length) * length +} + +export const deltaAngle = (current: number, target: number): number => { + let num = repeat(target - current, 360) + if (num > 180) num -= 360 + return num +} + +export const relativeAngle = (axis: Vector3, q1: Quaternion, q2: Quaternion): number => { + const vecA = multiplyQuaternionVector3(q1, axis) + const vecB = multiplyQuaternionVector3(q2, axis) + const angleA = MathUtils.radToDeg(Math.atan2(vecA.x, vecA.z)) + const angleB = MathUtils.radToDeg(Math.atan2(vecB.x, vecB.z)) + return deltaAngle(angleA, angleB) +}