123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
- import {FBXLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/FBXLoader.js';
- import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/GLTFLoader.js';
- class BasicCharacterControllerProxy {
- constructor(animations) {
- this._animations = animations;
- }
- get animations() {
- return this._animations;
- }
- };
- class BasicCharacterController {
- constructor(params) {
- this._Init(params);
- }
- _Init(params) {
- this._params = params;
- this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
- this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
- this._velocity = new THREE.Vector3(0, 0, 0);
- this._position = new THREE.Vector3();
- this._animations = {};
- this._input = new BasicCharacterControllerInput();
- this._stateMachine = new CharacterFSM(
- new BasicCharacterControllerProxy(this._animations));
- this._LoadModels();
- }
- _LoadModels() {
- const loader = new FBXLoader();
- loader.setPath('./resources/zombie/');
- loader.load('mremireh_o_desbiens.fbx', (fbx) => {
- fbx.scale.setScalar(0.1);
- fbx.traverse(c => {
- c.castShadow = true;
- });
- this._target = fbx;
- this._params.scene.add(this._target);
- this._mixer = new THREE.AnimationMixer(this._target);
- this._manager = new THREE.LoadingManager();
- this._manager.onLoad = () => {
- this._stateMachine.SetState('idle');
- };
- const _OnLoad = (animName, anim) => {
- const clip = anim.animations[0];
- const action = this._mixer.clipAction(clip);
-
- this._animations[animName] = {
- clip: clip,
- action: action,
- };
- };
- const loader = new FBXLoader(this._manager);
- loader.setPath('./resources/zombie/');
- loader.load('walk.fbx', (a) => { _OnLoad('walk', a); });
- loader.load('run.fbx', (a) => { _OnLoad('run', a); });
- loader.load('idle.fbx', (a) => { _OnLoad('idle', a); });
- loader.load('dance.fbx', (a) => { _OnLoad('dance', a); });
- });
- }
- get Position() {
- return this._position;
- }
- get Rotation() {
- if (!this._target) {
- return new THREE.Quaternion();
- }
- return this._target.quaternion;
- }
- Update(timeInSeconds) {
- if (!this._stateMachine._currentState) {
- return;
- }
- this._stateMachine.Update(timeInSeconds, this._input);
- const velocity = this._velocity;
- const frameDecceleration = new THREE.Vector3(
- velocity.x * this._decceleration.x,
- velocity.y * this._decceleration.y,
- velocity.z * this._decceleration.z
- );
- frameDecceleration.multiplyScalar(timeInSeconds);
- frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
- Math.abs(frameDecceleration.z), Math.abs(velocity.z));
- velocity.add(frameDecceleration);
- const controlObject = this._target;
- const _Q = new THREE.Quaternion();
- const _A = new THREE.Vector3();
- const _R = controlObject.quaternion.clone();
- const acc = this._acceleration.clone();
- if (this._input._keys.shift) {
- acc.multiplyScalar(2.0);
- }
- if (this._stateMachine._currentState.Name == 'dance') {
- acc.multiplyScalar(0.0);
- }
- if (this._input._keys.forward) {
- velocity.z += acc.z * timeInSeconds;
- }
- if (this._input._keys.backward) {
- velocity.z -= acc.z * timeInSeconds;
- }
- if (this._input._keys.left) {
- _A.set(0, 1, 0);
- _Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this._acceleration.y);
- _R.multiply(_Q);
- }
- if (this._input._keys.right) {
- _A.set(0, 1, 0);
- _Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this._acceleration.y);
- _R.multiply(_Q);
- }
- controlObject.quaternion.copy(_R);
- const oldPosition = new THREE.Vector3();
- oldPosition.copy(controlObject.position);
- const forward = new THREE.Vector3(0, 0, 1);
- forward.applyQuaternion(controlObject.quaternion);
- forward.normalize();
- const sideways = new THREE.Vector3(1, 0, 0);
- sideways.applyQuaternion(controlObject.quaternion);
- sideways.normalize();
- sideways.multiplyScalar(velocity.x * timeInSeconds);
- forward.multiplyScalar(velocity.z * timeInSeconds);
- controlObject.position.add(forward);
- controlObject.position.add(sideways);
- this._position.copy(controlObject.position);
- if (this._mixer) {
- this._mixer.update(timeInSeconds);
- }
- }
- };
- class BasicCharacterControllerInput {
- constructor() {
- this._Init();
- }
- _Init() {
- this._keys = {
- forward: false,
- backward: false,
- left: false,
- right: false,
- space: false,
- shift: false,
- };
- document.addEventListener('keydown', (e) => this._onKeyDown(e), false);
- document.addEventListener('keyup', (e) => this._onKeyUp(e), false);
- }
- _onKeyDown(event) {
- switch (event.keyCode) {
- case 87: // w
- this._keys.forward = true;
- break;
- case 65: // a
- this._keys.left = true;
- break;
- case 83: // s
- this._keys.backward = true;
- break;
- case 68: // d
- this._keys.right = true;
- break;
- case 32: // SPACE
- this._keys.space = true;
- break;
- case 16: // SHIFT
- this._keys.shift = true;
- break;
- }
- }
- _onKeyUp(event) {
- switch(event.keyCode) {
- case 87: // w
- this._keys.forward = false;
- break;
- case 65: // a
- this._keys.left = false;
- break;
- case 83: // s
- this._keys.backward = false;
- break;
- case 68: // d
- this._keys.right = false;
- break;
- case 32: // SPACE
- this._keys.space = false;
- break;
- case 16: // SHIFT
- this._keys.shift = false;
- break;
- }
- }
- };
- class FiniteStateMachine {
- constructor() {
- this._states = {};
- this._currentState = null;
- }
- _AddState(name, type) {
- this._states[name] = type;
- }
- SetState(name) {
- const prevState = this._currentState;
-
- if (prevState) {
- if (prevState.Name == name) {
- return;
- }
- prevState.Exit();
- }
- const state = new this._states[name](this);
- this._currentState = state;
- state.Enter(prevState);
- }
- Update(timeElapsed, input) {
- if (this._currentState) {
- this._currentState.Update(timeElapsed, input);
- }
- }
- };
- class CharacterFSM extends FiniteStateMachine {
- constructor(proxy) {
- super();
- this._proxy = proxy;
- this._Init();
- }
- _Init() {
- this._AddState('idle', IdleState);
- this._AddState('walk', WalkState);
- this._AddState('run', RunState);
- this._AddState('dance', DanceState);
- }
- };
- class State {
- constructor(parent) {
- this._parent = parent;
- }
- Enter() {}
- Exit() {}
- Update() {}
- };
- class DanceState extends State {
- constructor(parent) {
- super(parent);
- this._FinishedCallback = () => {
- this._Finished();
- }
- }
- get Name() {
- return 'dance';
- }
- Enter(prevState) {
- const curAction = this._parent._proxy._animations['dance'].action;
- const mixer = curAction.getMixer();
- mixer.addEventListener('finished', this._FinishedCallback);
- if (prevState) {
- const prevAction = this._parent._proxy._animations[prevState.Name].action;
- curAction.reset();
- curAction.setLoop(THREE.LoopOnce, 1);
- curAction.clampWhenFinished = true;
- curAction.crossFadeFrom(prevAction, 0.2, true);
- curAction.play();
- } else {
- curAction.play();
- }
- }
- _Finished() {
- this._Cleanup();
- this._parent.SetState('idle');
- }
- _Cleanup() {
- const action = this._parent._proxy._animations['dance'].action;
-
- action.getMixer().removeEventListener('finished', this._CleanupCallback);
- }
- Exit() {
- this._Cleanup();
- }
- Update(_) {
- }
- };
- class WalkState extends State {
- constructor(parent) {
- super(parent);
- }
- get Name() {
- return 'walk';
- }
- Enter(prevState) {
- const curAction = this._parent._proxy._animations['walk'].action;
- if (prevState) {
- const prevAction = this._parent._proxy._animations[prevState.Name].action;
- curAction.enabled = true;
- if (prevState.Name == 'run') {
- const ratio = curAction.getClip().duration / prevAction.getClip().duration;
- curAction.time = prevAction.time * ratio;
- } else {
- curAction.time = 0.0;
- curAction.setEffectiveTimeScale(1.0);
- curAction.setEffectiveWeight(1.0);
- }
- curAction.crossFadeFrom(prevAction, 0.5, true);
- curAction.play();
- } else {
- curAction.play();
- }
- }
- Exit() {
- }
- Update(timeElapsed, input) {
- if (input._keys.forward || input._keys.backward) {
- if (input._keys.shift) {
- this._parent.SetState('run');
- }
- return;
- }
- this._parent.SetState('idle');
- }
- };
- class RunState extends State {
- constructor(parent) {
- super(parent);
- }
- get Name() {
- return 'run';
- }
- Enter(prevState) {
- const curAction = this._parent._proxy._animations['run'].action;
- if (prevState) {
- const prevAction = this._parent._proxy._animations[prevState.Name].action;
- curAction.enabled = true;
- if (prevState.Name == 'walk') {
- const ratio = curAction.getClip().duration / prevAction.getClip().duration;
- curAction.time = prevAction.time * ratio;
- } else {
- curAction.time = 0.0;
- curAction.setEffectiveTimeScale(1.0);
- curAction.setEffectiveWeight(1.0);
- }
- curAction.crossFadeFrom(prevAction, 0.5, true);
- curAction.play();
- } else {
- curAction.play();
- }
- }
- Exit() {
- }
- Update(timeElapsed, input) {
- if (input._keys.forward || input._keys.backward) {
- if (!input._keys.shift) {
- this._parent.SetState('walk');
- }
- return;
- }
- this._parent.SetState('idle');
- }
- };
- class IdleState extends State {
- constructor(parent) {
- super(parent);
- }
- get Name() {
- return 'idle';
- }
- Enter(prevState) {
- const idleAction = this._parent._proxy._animations['idle'].action;
- if (prevState) {
- const prevAction = this._parent._proxy._animations[prevState.Name].action;
- idleAction.time = 0.0;
- idleAction.enabled = true;
- idleAction.setEffectiveTimeScale(1.0);
- idleAction.setEffectiveWeight(1.0);
- idleAction.crossFadeFrom(prevAction, 0.5, true);
- idleAction.play();
- } else {
- idleAction.play();
- }
- }
- Exit() {
- }
- Update(_, input) {
- if (input._keys.forward || input._keys.backward) {
- this._parent.SetState('walk');
- } else if (input._keys.space) {
- this._parent.SetState('dance');
- }
- }
- };
- class ThirdPersonCamera {
- constructor(params) {
- this._params = params;
- this._camera = params.camera;
- this._currentPosition = new THREE.Vector3();
- this._currentLookat = new THREE.Vector3();
- }
- _CalculateIdealOffset() {
- const idealOffset = new THREE.Vector3(-15, 20, -30);
- idealOffset.applyQuaternion(this._params.target.Rotation);
- idealOffset.add(this._params.target.Position);
- return idealOffset;
- }
- _CalculateIdealLookat() {
- const idealLookat = new THREE.Vector3(0, 10, 50);
- idealLookat.applyQuaternion(this._params.target.Rotation);
- idealLookat.add(this._params.target.Position);
- return idealLookat;
- }
- Update(timeElapsed) {
- const idealOffset = this._CalculateIdealOffset();
- const idealLookat = this._CalculateIdealLookat();
- // const t = 0.05;
- // const t = 4.0 * timeElapsed;
- const t = 1.0 - Math.pow(0.001, timeElapsed);
- this._currentPosition.lerp(idealOffset, t);
- this._currentLookat.lerp(idealLookat, t);
- this._camera.position.copy(this._currentPosition);
- this._camera.lookAt(this._currentLookat);
- }
- }
- class ThirdPersonCameraDemo {
- constructor() {
- this._Initialize();
- }
- _Initialize() {
- this._threejs = new THREE.WebGLRenderer({
- antialias: true,
- });
- this._threejs.outputEncoding = THREE.sRGBEncoding;
- this._threejs.shadowMap.enabled = true;
- this._threejs.shadowMap.type = THREE.PCFSoftShadowMap;
- this._threejs.setPixelRatio(window.devicePixelRatio);
- this._threejs.setSize(window.innerWidth, window.innerHeight);
- document.body.appendChild(this._threejs.domElement);
- window.addEventListener('resize', () => {
- this._OnWindowResize();
- }, false);
- const fov = 60;
- const aspect = 1920 / 1080;
- const near = 1.0;
- const far = 1000.0;
- this._camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
- this._camera.position.set(25, 10, 25);
- this._scene = new THREE.Scene();
- let light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
- light.position.set(-100, 100, 100);
- light.target.position.set(0, 0, 0);
- light.castShadow = true;
- light.shadow.bias = -0.001;
- light.shadow.mapSize.width = 4096;
- light.shadow.mapSize.height = 4096;
- light.shadow.camera.near = 0.1;
- light.shadow.camera.far = 500.0;
- light.shadow.camera.near = 0.5;
- light.shadow.camera.far = 500.0;
- light.shadow.camera.left = 50;
- light.shadow.camera.right = -50;
- light.shadow.camera.top = 50;
- light.shadow.camera.bottom = -50;
- this._scene.add(light);
- light = new THREE.AmbientLight(0xFFFFFF, 0.25);
- this._scene.add(light);
- const loader = new THREE.CubeTextureLoader();
- const texture = loader.load([
- './resources/posx.jpg',
- './resources/negx.jpg',
- './resources/posy.jpg',
- './resources/negy.jpg',
- './resources/posz.jpg',
- './resources/negz.jpg',
- ]);
- texture.encoding = THREE.sRGBEncoding;
- this._scene.background = texture;
- const plane = new THREE.Mesh(
- new THREE.PlaneGeometry(100, 100, 10, 10),
- new THREE.MeshStandardMaterial({
- color: 0x808080,
- }));
- plane.castShadow = false;
- plane.receiveShadow = true;
- plane.rotation.x = -Math.PI / 2;
- this._scene.add(plane);
- this._mixers = [];
- this._previousRAF = null;
- this._LoadAnimatedModel();
- this._RAF();
- }
- _LoadAnimatedModel() {
- const params = {
- camera: this._camera,
- scene: this._scene,
- }
- this._controls = new BasicCharacterController(params);
- this._thirdPersonCamera = new ThirdPersonCamera({
- camera: this._camera,
- target: this._controls,
- });
- }
- _OnWindowResize() {
- this._camera.aspect = window.innerWidth / window.innerHeight;
- this._camera.updateProjectionMatrix();
- this._threejs.setSize(window.innerWidth, window.innerHeight);
- }
- _RAF() {
- requestAnimationFrame((t) => {
- if (this._previousRAF === null) {
- this._previousRAF = t;
- }
- this._RAF();
- this._threejs.render(this._scene, this._camera);
- this._Step(t - this._previousRAF);
- this._previousRAF = t;
- });
- }
- _Step(timeElapsed) {
- const timeElapsedS = timeElapsed * 0.001;
- if (this._mixers) {
- this._mixers.map(m => m.update(timeElapsedS));
- }
- if (this._controls) {
- this._controls.Update(timeElapsedS);
- }
- this._thirdPersonCamera.Update(timeElapsedS);
- }
- }
- let _APP = null;
- window.addEventListener('DOMContentLoaded', () => {
- _APP = new ThirdPersonCameraDemo();
- });
- function _LerpOverFrames(frames, t) {
- const s = new THREE.Vector3(0, 0, 0);
- const e = new THREE.Vector3(100, 0, 0);
- const c = s.clone();
- for (let i = 0; i < frames; i++) {
- c.lerp(e, t);
- }
- return c;
- }
- function _TestLerp(t1, t2) {
- const v1 = _LerpOverFrames(100, t1);
- const v2 = _LerpOverFrames(50, t2);
- console.log(v1.x + ' | ' + v2.x);
- }
- _TestLerp(0.01, 0.01);
- _TestLerp(1.0 / 100.0, 1.0 / 50.0);
- _TestLerp(1.0 - Math.pow(0.3, 1.0 / 100.0),
- 1.0 - Math.pow(0.3, 1.0 / 50.0));
|