From 9b29855a4aca52dd4a4ea0f1fe6417f0acc9b63e Mon Sep 17 00:00:00 2001 From: Ian Mancini Date: Tue, 8 Dec 2020 07:04:26 -0300 Subject: [PATCH] Add human model --- packages/client/public/model/human.glb | 3 + packages/client/src/3d/Phantom.tsx | 21 +--- packages/client/src/3d/Player.tsx | 48 +++---- packages/client/src/3d/models/Human.tsx | 160 ++++++++++++++++++++++++ packages/client/src/store.ts | 5 +- 5 files changed, 197 insertions(+), 40 deletions(-) create mode 100644 packages/client/public/model/human.glb create mode 100644 packages/client/src/3d/models/Human.tsx diff --git a/packages/client/public/model/human.glb b/packages/client/public/model/human.glb new file mode 100644 index 0000000..212c7ce --- /dev/null +++ b/packages/client/public/model/human.glb @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a617ab4a4ecaa83e6557a7016935328bc80a1c093079fb76b6c4c92e790e4cf8 +size 793076 diff --git a/packages/client/src/3d/Phantom.tsx b/packages/client/src/3d/Phantom.tsx index 4a7f11e..7413e0d 100644 --- a/packages/client/src/3d/Phantom.tsx +++ b/packages/client/src/3d/Phantom.tsx @@ -1,6 +1,7 @@ // @ts-nocheck import React, { useEffect, useRef } from 'react' import { Vector3, Euler } from 'three' +import Human from './models/Human' import api, { Transform } from '../store' @@ -10,6 +11,7 @@ type PhantomProps = { const Phantom: React.FC = ({ id }) => { const ref = useRef(null) + const currentAnimation = useRef(0) useEffect(() => { if (ref.current) { @@ -20,10 +22,11 @@ const Phantom: React.FC = ({ id }) => { } ref.current?.position.fromArray([ state.position[0], - state.position[1] + 0.25, + state.position[1], state.position[2], ]) - ref.current?.quaternion.fromArray(state.rotation) + ref.current?.quaternion.fromArray([0, state.rotation[1], 0, state.rotation[3]]) + currentAnimation.current = state.a }, (state) => state?.userTransforms?.[id], ) @@ -32,19 +35,7 @@ const Phantom: React.FC = ({ id }) => { return ( - - - - - - - - - + ) } diff --git a/packages/client/src/3d/Player.tsx b/packages/client/src/3d/Player.tsx index c6ad979..e79e318 100644 --- a/packages/client/src/3d/Player.tsx +++ b/packages/client/src/3d/Player.tsx @@ -16,9 +16,10 @@ import WorldCollisions from './models/WorldCollisions' import useStore from '../store' import { MeshBasicMaterial } from 'three/src/materials/MeshBasicMaterial' import { Quaternion } from 'three/src/math/Quaternion' +import { ActionName } from './models/Human' -const SPEED = 1 -const HEIGHT = 1.5 +const SPEED = 0.6 +export const HEIGHT = 1.5 const CIRCLE_RADIUS = 1.0 const CIRCLE_SEGMENTS = 8 const InitialPosition = new Vector3(49.92, 3.15, 34.52) @@ -37,18 +38,18 @@ 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, +export const animations: Record = { + idle: 'idle', + turn_right: 'turn_right', + turn_left: 'turn_left', + walk_backwards: 'walk_backwards', + walk: 'walk', + walk_right: 'walk_right', + walk_left: 'walk_left', + run: 'run', + run_right: 'run_right', + run_left: 'run_left', + jump: 'jump', } function FirstPersonCamera(props: JSX.IntrinsicElements['perspectiveCamera']) { @@ -146,9 +147,8 @@ const Player = () => { pCameraQuaternion.current, ) - if (Math.abs(rotationAngle) > 10) { - anim = - rotationAngle > 1 ? animations.turn_right : (anim = animations.turn_left) + if (Math.abs(rotationAngle) > 0.5) { + anim = rotationAngle > 1 ? animations.turn_right : animations.turn_left } else { anim = animations.idle } @@ -170,12 +170,12 @@ const Player = () => { groupRef.current?.position.z, ], rotation: [...cameraRotation], - a: anim, + animation: anim, }) pCameraQuaternion.current.copy(camera.quaternion) } - }, 1000) + }, 33) return () => { clearInterval(socketEmitTransformInterval) @@ -202,8 +202,8 @@ const Player = () => { if (pointerLocked) { // Slowdown - velocity.current.x -= velocity.current.x * 10.0 * delta - velocity.current.z -= velocity.current.z * 10.0 * delta + velocity.current.x -= velocity.current.x * 9.0 * delta + velocity.current.z -= velocity.current.z * 9.0 * delta // Fall bottomRaycaster.current.ray.origin.copy(groupRef.current.position) @@ -215,7 +215,7 @@ const Player = () => { ) if (intersections.length < 1) { - velocity.current.y -= 9.8 * 10 * delta + velocity.current.y -= 9.8 * 5 * delta } else { velocity.current.y = 0 groupRef.current.position.y = intersections[0].point.y + HEIGHT @@ -227,7 +227,7 @@ const Player = () => { direction.current.normalize() // Running - speed.current = run && direction.current.x >= 0 ? 1.5 : 1 + speed.current = run && direction.current.x >= 0 ? SPEED * 1.5 : SPEED // Move if (forward || backward) @@ -278,7 +278,7 @@ const Player = () => { return ( <> - + + +const Human: React.FC = ({ + id, + ...rest +}) => { + const group = useRef() + const { nodes, animations } = useGLTF('/model/human.glb') as GLTFResult + const currentAnimation = useRef('idle') + + const material = useMemo(() => { + const m = new MeshNormalMaterial({ + transparent: true, + side: FrontSide, + flatShading: false, + skinning: true, + }) + + m.onBeforeCompile = function (shader) { + console.log(shader.fragmentShader) + shader.fragmentShader = shader.fragmentShader.replace( + 'gl_FragColor = vec4( packNormalToRGB( normal ), opacity );', + [ + 'gl_FragColor = vec4( packNormalToRGB( normal ), opacity );', + 'gl_FragColor.a = 1.0 - pow( gl_FragCoord.z, 0.75 );', + 'gl_FragColor.r = 0.70;', + 'gl_FragColor.g = 0.85;', + 'gl_FragColor.b = 1.0;', + ].join('\n'), + ) + } + + m.needsUpdate = true + return m + }, []) + + const actions = useRef() + const [mixer] = useState(() => new THREE.AnimationMixer(nodes.mesh)) + + useEffect(() => { + api.subscribe( + (state: Transform | null) => { + if (!state || !actions.current) { + return + } + if (state.animation === currentAnimation.current) return + + const newAction = actions.current[state.animation] + const previousAction = actions.current[currentAnimation.current] + + newAction.enabled = true + newAction.setEffectiveTimeScale(1) + newAction.setEffectiveWeight(1) + previousAction.crossFadeTo(newAction, 0.2, true) + currentAnimation.current = state.animation + }, + (state) => state.userTransforms[id], + ) + }, [id]) + + useEffect(() => { + if (actions.current && currentAnimation.current) { + } + }, [currentAnimation.current]) + + useFrame((_, delta) => { + mixer.update(delta) + }) + + useEffect(() => { + actions.current = { + idle: mixer.clipAction(animations[0], group.current), + jump: mixer.clipAction(animations[1], group.current), + run_left: mixer.clipAction(animations[2], group.current), + run_right: mixer.clipAction(animations[3], group.current), + run: mixer.clipAction(animations[4], group.current), + turn_left: mixer.clipAction(animations[5], group.current), + turn_right: mixer.clipAction(animations[6], group.current), + walk: mixer.clipAction(animations[7], group.current), + walk_backwards: mixer.clipAction(animations[8], group.current), + walk_left: mixer.clipAction(animations[9], group.current), + walk_right: mixer.clipAction(animations[10], group.current), + } + + Object.values(actions.current).forEach((a) => { + a.setEffectiveWeight(0) + a.enabled = true + a.setEffectiveTimeScale(1) + a.play() + }) + + if (currentAnimation.current) { + actions.current[currentAnimation.current].play() + } + }, [group, animations, mixer]) + + return ( + + + + + + + ) +} + +useGLTF.preload('/model/human.glb') + +export default Human diff --git a/packages/client/src/store.ts b/packages/client/src/store.ts index a2fa8fc..71fc4bb 100644 --- a/packages/client/src/store.ts +++ b/packages/client/src/store.ts @@ -2,18 +2,21 @@ import create from 'zustand' import { PointerLockControls } from '@react-three/drei' import { Socket } from 'socket.io-client' +import { ActionName } from './3d/models/Human' + type Error = null | 'SOCKET' export type Transform = { position: number[] rotation: number[] + animation: ActionName } type userId = string export type UserTransforms = Record -type State = { +export type State = { pointerLockControls: PointerLockControls | undefined setPointerLockControls: (controls: PointerLockControls) => void pointerLocked: boolean