main.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {FBXLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/FBXLoader.js';
  3. import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/GLTFLoader.js';
  4. class BasicCharacterControllerProxy {
  5. constructor(animations) {
  6. this._animations = animations;
  7. }
  8. get animations() {
  9. return this._animations;
  10. }
  11. };
  12. class BasicCharacterController {
  13. constructor(params) {
  14. this._Init(params);
  15. }
  16. _Init(params) {
  17. this._params = params;
  18. this._decceleration = new THREE.Vector3(-0.0005, -0.0001, -5.0);
  19. this._acceleration = new THREE.Vector3(1, 0.25, 50.0);
  20. this._velocity = new THREE.Vector3(0, 0, 0);
  21. this._position = new THREE.Vector3();
  22. this._animations = {};
  23. this._input = new BasicCharacterControllerInput();
  24. this._stateMachine = new CharacterFSM(
  25. new BasicCharacterControllerProxy(this._animations));
  26. this._LoadModels();
  27. }
  28. _LoadModels() {
  29. const loader = new FBXLoader();
  30. loader.setPath('./resources/zombie/');
  31. loader.load('mremireh_o_desbiens.fbx', (fbx) => {
  32. fbx.scale.setScalar(0.1);
  33. fbx.traverse(c => {
  34. c.castShadow = true;
  35. });
  36. this._target = fbx;
  37. this._params.scene.add(this._target);
  38. this._mixer = new THREE.AnimationMixer(this._target);
  39. this._manager = new THREE.LoadingManager();
  40. this._manager.onLoad = () => {
  41. this._stateMachine.SetState('idle');
  42. };
  43. const _OnLoad = (animName, anim) => {
  44. const clip = anim.animations[0];
  45. const action = this._mixer.clipAction(clip);
  46. this._animations[animName] = {
  47. clip: clip,
  48. action: action,
  49. };
  50. };
  51. const loader = new FBXLoader(this._manager);
  52. loader.setPath('./resources/zombie/');
  53. loader.load('walk.fbx', (a) => { _OnLoad('walk', a); });
  54. loader.load('run.fbx', (a) => { _OnLoad('run', a); });
  55. loader.load('idle.fbx', (a) => { _OnLoad('idle', a); });
  56. loader.load('dance.fbx', (a) => { _OnLoad('dance', a); });
  57. });
  58. }
  59. get Position() {
  60. return this._position;
  61. }
  62. get Rotation() {
  63. if (!this._target) {
  64. return new THREE.Quaternion();
  65. }
  66. return this._target.quaternion;
  67. }
  68. Update(timeInSeconds) {
  69. if (!this._stateMachine._currentState) {
  70. return;
  71. }
  72. this._stateMachine.Update(timeInSeconds, this._input);
  73. const velocity = this._velocity;
  74. const frameDecceleration = new THREE.Vector3(
  75. velocity.x * this._decceleration.x,
  76. velocity.y * this._decceleration.y,
  77. velocity.z * this._decceleration.z
  78. );
  79. frameDecceleration.multiplyScalar(timeInSeconds);
  80. frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
  81. Math.abs(frameDecceleration.z), Math.abs(velocity.z));
  82. velocity.add(frameDecceleration);
  83. const controlObject = this._target;
  84. const _Q = new THREE.Quaternion();
  85. const _A = new THREE.Vector3();
  86. const _R = controlObject.quaternion.clone();
  87. const acc = this._acceleration.clone();
  88. if (this._input._keys.shift) {
  89. acc.multiplyScalar(2.0);
  90. }
  91. if (this._stateMachine._currentState.Name == 'dance') {
  92. acc.multiplyScalar(0.0);
  93. }
  94. if (this._input._keys.forward) {
  95. velocity.z += acc.z * timeInSeconds;
  96. }
  97. if (this._input._keys.backward) {
  98. velocity.z -= acc.z * timeInSeconds;
  99. }
  100. if (this._input._keys.left) {
  101. _A.set(0, 1, 0);
  102. _Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this._acceleration.y);
  103. _R.multiply(_Q);
  104. }
  105. if (this._input._keys.right) {
  106. _A.set(0, 1, 0);
  107. _Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this._acceleration.y);
  108. _R.multiply(_Q);
  109. }
  110. controlObject.quaternion.copy(_R);
  111. const oldPosition = new THREE.Vector3();
  112. oldPosition.copy(controlObject.position);
  113. const forward = new THREE.Vector3(0, 0, 1);
  114. forward.applyQuaternion(controlObject.quaternion);
  115. forward.normalize();
  116. const sideways = new THREE.Vector3(1, 0, 0);
  117. sideways.applyQuaternion(controlObject.quaternion);
  118. sideways.normalize();
  119. sideways.multiplyScalar(velocity.x * timeInSeconds);
  120. forward.multiplyScalar(velocity.z * timeInSeconds);
  121. controlObject.position.add(forward);
  122. controlObject.position.add(sideways);
  123. this._position.copy(controlObject.position);
  124. if (this._mixer) {
  125. this._mixer.update(timeInSeconds);
  126. }
  127. }
  128. };
  129. class BasicCharacterControllerInput {
  130. constructor() {
  131. this._Init();
  132. }
  133. _Init() {
  134. this._keys = {
  135. forward: false,
  136. backward: false,
  137. left: false,
  138. right: false,
  139. space: false,
  140. shift: false,
  141. };
  142. document.addEventListener('keydown', (e) => this._onKeyDown(e), false);
  143. document.addEventListener('keyup', (e) => this._onKeyUp(e), false);
  144. }
  145. _onKeyDown(event) {
  146. switch (event.keyCode) {
  147. case 87: // w
  148. this._keys.forward = true;
  149. break;
  150. case 65: // a
  151. this._keys.left = true;
  152. break;
  153. case 83: // s
  154. this._keys.backward = true;
  155. break;
  156. case 68: // d
  157. this._keys.right = true;
  158. break;
  159. case 32: // SPACE
  160. this._keys.space = true;
  161. break;
  162. case 16: // SHIFT
  163. this._keys.shift = true;
  164. break;
  165. }
  166. }
  167. _onKeyUp(event) {
  168. switch(event.keyCode) {
  169. case 87: // w
  170. this._keys.forward = false;
  171. break;
  172. case 65: // a
  173. this._keys.left = false;
  174. break;
  175. case 83: // s
  176. this._keys.backward = false;
  177. break;
  178. case 68: // d
  179. this._keys.right = false;
  180. break;
  181. case 32: // SPACE
  182. this._keys.space = false;
  183. break;
  184. case 16: // SHIFT
  185. this._keys.shift = false;
  186. break;
  187. }
  188. }
  189. };
  190. class FiniteStateMachine {
  191. constructor() {
  192. this._states = {};
  193. this._currentState = null;
  194. }
  195. _AddState(name, type) {
  196. this._states[name] = type;
  197. }
  198. SetState(name) {
  199. const prevState = this._currentState;
  200. if (prevState) {
  201. if (prevState.Name == name) {
  202. return;
  203. }
  204. prevState.Exit();
  205. }
  206. const state = new this._states[name](this);
  207. this._currentState = state;
  208. state.Enter(prevState);
  209. }
  210. Update(timeElapsed, input) {
  211. if (this._currentState) {
  212. this._currentState.Update(timeElapsed, input);
  213. }
  214. }
  215. };
  216. class CharacterFSM extends FiniteStateMachine {
  217. constructor(proxy) {
  218. super();
  219. this._proxy = proxy;
  220. this._Init();
  221. }
  222. _Init() {
  223. this._AddState('idle', IdleState);
  224. this._AddState('walk', WalkState);
  225. this._AddState('run', RunState);
  226. this._AddState('dance', DanceState);
  227. }
  228. };
  229. class State {
  230. constructor(parent) {
  231. this._parent = parent;
  232. }
  233. Enter() {}
  234. Exit() {}
  235. Update() {}
  236. };
  237. class DanceState extends State {
  238. constructor(parent) {
  239. super(parent);
  240. this._FinishedCallback = () => {
  241. this._Finished();
  242. }
  243. }
  244. get Name() {
  245. return 'dance';
  246. }
  247. Enter(prevState) {
  248. const curAction = this._parent._proxy._animations['dance'].action;
  249. const mixer = curAction.getMixer();
  250. mixer.addEventListener('finished', this._FinishedCallback);
  251. if (prevState) {
  252. const prevAction = this._parent._proxy._animations[prevState.Name].action;
  253. curAction.reset();
  254. curAction.setLoop(THREE.LoopOnce, 1);
  255. curAction.clampWhenFinished = true;
  256. curAction.crossFadeFrom(prevAction, 0.2, true);
  257. curAction.play();
  258. } else {
  259. curAction.play();
  260. }
  261. }
  262. _Finished() {
  263. this._Cleanup();
  264. this._parent.SetState('idle');
  265. }
  266. _Cleanup() {
  267. const action = this._parent._proxy._animations['dance'].action;
  268. action.getMixer().removeEventListener('finished', this._CleanupCallback);
  269. }
  270. Exit() {
  271. this._Cleanup();
  272. }
  273. Update(_) {
  274. }
  275. };
  276. class WalkState extends State {
  277. constructor(parent) {
  278. super(parent);
  279. }
  280. get Name() {
  281. return 'walk';
  282. }
  283. Enter(prevState) {
  284. const curAction = this._parent._proxy._animations['walk'].action;
  285. if (prevState) {
  286. const prevAction = this._parent._proxy._animations[prevState.Name].action;
  287. curAction.enabled = true;
  288. if (prevState.Name == 'run') {
  289. const ratio = curAction.getClip().duration / prevAction.getClip().duration;
  290. curAction.time = prevAction.time * ratio;
  291. } else {
  292. curAction.time = 0.0;
  293. curAction.setEffectiveTimeScale(1.0);
  294. curAction.setEffectiveWeight(1.0);
  295. }
  296. curAction.crossFadeFrom(prevAction, 0.5, true);
  297. curAction.play();
  298. } else {
  299. curAction.play();
  300. }
  301. }
  302. Exit() {
  303. }
  304. Update(timeElapsed, input) {
  305. if (input._keys.forward || input._keys.backward) {
  306. if (input._keys.shift) {
  307. this._parent.SetState('run');
  308. }
  309. return;
  310. }
  311. this._parent.SetState('idle');
  312. }
  313. };
  314. class RunState extends State {
  315. constructor(parent) {
  316. super(parent);
  317. }
  318. get Name() {
  319. return 'run';
  320. }
  321. Enter(prevState) {
  322. const curAction = this._parent._proxy._animations['run'].action;
  323. if (prevState) {
  324. const prevAction = this._parent._proxy._animations[prevState.Name].action;
  325. curAction.enabled = true;
  326. if (prevState.Name == 'walk') {
  327. const ratio = curAction.getClip().duration / prevAction.getClip().duration;
  328. curAction.time = prevAction.time * ratio;
  329. } else {
  330. curAction.time = 0.0;
  331. curAction.setEffectiveTimeScale(1.0);
  332. curAction.setEffectiveWeight(1.0);
  333. }
  334. curAction.crossFadeFrom(prevAction, 0.5, true);
  335. curAction.play();
  336. } else {
  337. curAction.play();
  338. }
  339. }
  340. Exit() {
  341. }
  342. Update(timeElapsed, input) {
  343. if (input._keys.forward || input._keys.backward) {
  344. if (!input._keys.shift) {
  345. this._parent.SetState('walk');
  346. }
  347. return;
  348. }
  349. this._parent.SetState('idle');
  350. }
  351. };
  352. class IdleState extends State {
  353. constructor(parent) {
  354. super(parent);
  355. }
  356. get Name() {
  357. return 'idle';
  358. }
  359. Enter(prevState) {
  360. const idleAction = this._parent._proxy._animations['idle'].action;
  361. if (prevState) {
  362. const prevAction = this._parent._proxy._animations[prevState.Name].action;
  363. idleAction.time = 0.0;
  364. idleAction.enabled = true;
  365. idleAction.setEffectiveTimeScale(1.0);
  366. idleAction.setEffectiveWeight(1.0);
  367. idleAction.crossFadeFrom(prevAction, 0.5, true);
  368. idleAction.play();
  369. } else {
  370. idleAction.play();
  371. }
  372. }
  373. Exit() {
  374. }
  375. Update(_, input) {
  376. if (input._keys.forward || input._keys.backward) {
  377. this._parent.SetState('walk');
  378. } else if (input._keys.space) {
  379. this._parent.SetState('dance');
  380. }
  381. }
  382. };
  383. class ThirdPersonCamera {
  384. constructor(params) {
  385. this._params = params;
  386. this._camera = params.camera;
  387. this._currentPosition = new THREE.Vector3();
  388. this._currentLookat = new THREE.Vector3();
  389. }
  390. _CalculateIdealOffset() {
  391. const idealOffset = new THREE.Vector3(-15, 20, -30);
  392. idealOffset.applyQuaternion(this._params.target.Rotation);
  393. idealOffset.add(this._params.target.Position);
  394. return idealOffset;
  395. }
  396. _CalculateIdealLookat() {
  397. const idealLookat = new THREE.Vector3(0, 10, 50);
  398. idealLookat.applyQuaternion(this._params.target.Rotation);
  399. idealLookat.add(this._params.target.Position);
  400. return idealLookat;
  401. }
  402. Update(timeElapsed) {
  403. const idealOffset = this._CalculateIdealOffset();
  404. const idealLookat = this._CalculateIdealLookat();
  405. // const t = 0.05;
  406. // const t = 4.0 * timeElapsed;
  407. const t = 1.0 - Math.pow(0.001, timeElapsed);
  408. this._currentPosition.lerp(idealOffset, t);
  409. this._currentLookat.lerp(idealLookat, t);
  410. this._camera.position.copy(this._currentPosition);
  411. this._camera.lookAt(this._currentLookat);
  412. }
  413. }
  414. class ThirdPersonCameraDemo {
  415. constructor() {
  416. this._Initialize();
  417. }
  418. _Initialize() {
  419. this._threejs = new THREE.WebGLRenderer({
  420. antialias: true,
  421. });
  422. this._threejs.outputEncoding = THREE.sRGBEncoding;
  423. this._threejs.shadowMap.enabled = true;
  424. this._threejs.shadowMap.type = THREE.PCFSoftShadowMap;
  425. this._threejs.setPixelRatio(window.devicePixelRatio);
  426. this._threejs.setSize(window.innerWidth, window.innerHeight);
  427. document.body.appendChild(this._threejs.domElement);
  428. window.addEventListener('resize', () => {
  429. this._OnWindowResize();
  430. }, false);
  431. const fov = 60;
  432. const aspect = 1920 / 1080;
  433. const near = 1.0;
  434. const far = 1000.0;
  435. this._camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
  436. this._camera.position.set(25, 10, 25);
  437. this._scene = new THREE.Scene();
  438. let light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
  439. light.position.set(-100, 100, 100);
  440. light.target.position.set(0, 0, 0);
  441. light.castShadow = true;
  442. light.shadow.bias = -0.001;
  443. light.shadow.mapSize.width = 4096;
  444. light.shadow.mapSize.height = 4096;
  445. light.shadow.camera.near = 0.1;
  446. light.shadow.camera.far = 500.0;
  447. light.shadow.camera.near = 0.5;
  448. light.shadow.camera.far = 500.0;
  449. light.shadow.camera.left = 50;
  450. light.shadow.camera.right = -50;
  451. light.shadow.camera.top = 50;
  452. light.shadow.camera.bottom = -50;
  453. this._scene.add(light);
  454. light = new THREE.AmbientLight(0xFFFFFF, 0.25);
  455. this._scene.add(light);
  456. const loader = new THREE.CubeTextureLoader();
  457. const texture = loader.load([
  458. './resources/posx.jpg',
  459. './resources/negx.jpg',
  460. './resources/posy.jpg',
  461. './resources/negy.jpg',
  462. './resources/posz.jpg',
  463. './resources/negz.jpg',
  464. ]);
  465. texture.encoding = THREE.sRGBEncoding;
  466. this._scene.background = texture;
  467. const plane = new THREE.Mesh(
  468. new THREE.PlaneGeometry(100, 100, 10, 10),
  469. new THREE.MeshStandardMaterial({
  470. color: 0x808080,
  471. }));
  472. plane.castShadow = false;
  473. plane.receiveShadow = true;
  474. plane.rotation.x = -Math.PI / 2;
  475. this._scene.add(plane);
  476. this._mixers = [];
  477. this._previousRAF = null;
  478. this._LoadAnimatedModel();
  479. this._RAF();
  480. }
  481. _LoadAnimatedModel() {
  482. const params = {
  483. camera: this._camera,
  484. scene: this._scene,
  485. }
  486. this._controls = new BasicCharacterController(params);
  487. this._thirdPersonCamera = new ThirdPersonCamera({
  488. camera: this._camera,
  489. target: this._controls,
  490. });
  491. }
  492. _OnWindowResize() {
  493. this._camera.aspect = window.innerWidth / window.innerHeight;
  494. this._camera.updateProjectionMatrix();
  495. this._threejs.setSize(window.innerWidth, window.innerHeight);
  496. }
  497. _RAF() {
  498. requestAnimationFrame((t) => {
  499. if (this._previousRAF === null) {
  500. this._previousRAF = t;
  501. }
  502. this._RAF();
  503. this._threejs.render(this._scene, this._camera);
  504. this._Step(t - this._previousRAF);
  505. this._previousRAF = t;
  506. });
  507. }
  508. _Step(timeElapsed) {
  509. const timeElapsedS = timeElapsed * 0.001;
  510. if (this._mixers) {
  511. this._mixers.map(m => m.update(timeElapsedS));
  512. }
  513. if (this._controls) {
  514. this._controls.Update(timeElapsedS);
  515. }
  516. this._thirdPersonCamera.Update(timeElapsedS);
  517. }
  518. }
  519. let _APP = null;
  520. window.addEventListener('DOMContentLoaded', () => {
  521. _APP = new ThirdPersonCameraDemo();
  522. });
  523. function _LerpOverFrames(frames, t) {
  524. const s = new THREE.Vector3(0, 0, 0);
  525. const e = new THREE.Vector3(100, 0, 0);
  526. const c = s.clone();
  527. for (let i = 0; i < frames; i++) {
  528. c.lerp(e, t);
  529. }
  530. return c;
  531. }
  532. function _TestLerp(t1, t2) {
  533. const v1 = _LerpOverFrames(100, t1);
  534. const v2 = _LerpOverFrames(50, t2);
  535. console.log(v1.x + ' | ' + v2.x);
  536. }
  537. _TestLerp(0.01, 0.01);
  538. _TestLerp(1.0 / 100.0, 1.0 / 50.0);
  539. _TestLerp(1.0 - Math.pow(0.3, 1.0 / 100.0),
  540. 1.0 - Math.pow(0.3, 1.0 / 50.0));