main.js 15 KB

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