main.js 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {OrbitControls} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/controls/OrbitControls.js';
  3. import {FBXLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/FBXLoader.js';
  4. import 'https://cdn.jsdelivr.net/npm/[email protected]/dist/socket.io.js';
  5. const _CHARACTER_MODELS = {
  6. zombie: {
  7. base: 'mremireh_o_desbiens.fbx',
  8. path: './resources/characters/zombie/',
  9. animations: {
  10. idle: 'idle.fbx',
  11. walk: 'walk.fbx',
  12. run: 'run.fbx',
  13. dance: 'dance.fbx',
  14. },
  15. nameOffset: 25,
  16. },
  17. guard: {
  18. base: 'castle_guard_01.fbx',
  19. path: './resources/characters/guard/',
  20. animations: {
  21. idle: 'Sword And Shield Idle.fbx',
  22. walk: 'Sword And Shield Walk.fbx',
  23. run: 'Sword And Shield Run.fbx',
  24. dance: 'Macarena Dance.fbx',
  25. },
  26. nameOffset: 20,
  27. }
  28. }
  29. class FloatingName {
  30. constructor(params) {
  31. this.params_ = params;
  32. this.Init_();
  33. }
  34. Destroy() {
  35. this.element_ = null;
  36. }
  37. Init_() {
  38. const modelData = _CHARACTER_MODELS[this.params_.desc.character.class];
  39. this.element_ = document.createElement('canvas');
  40. this.context2d_ = this.element_.getContext('2d');
  41. this.context2d_.canvas.width = 256;
  42. this.context2d_.canvas.height = 128;
  43. this.context2d_.fillStyle = '#FFF';
  44. this.context2d_.font = "18pt Helvetica";
  45. this.context2d_.shadowOffsetX = 3;
  46. this.context2d_.shadowOffsetY = 3;
  47. this.context2d_.shadowColor = "rgba(0,0,0,0.3)";
  48. this.context2d_.shadowBlur = 4;
  49. this.context2d_.textAlign = 'center';
  50. this.context2d_.fillText(this.params_.desc.account.name, 128, 64);
  51. const map = new THREE.CanvasTexture(this.context2d_.canvas);
  52. this.sprite_ = new THREE.Sprite(
  53. new THREE.SpriteMaterial({map: map, color: 0xffffff}));
  54. this.sprite_.scale.set(20, 10, 1)
  55. this.sprite_.position.y += modelData.nameOffset;
  56. this.params_.parent.add(this.sprite_);
  57. }
  58. };
  59. class OurLoadingManager {
  60. constructor(loader) {
  61. this.loader_ = loader;
  62. this.files_ = new Set();
  63. this.onLoad = () => {};
  64. }
  65. load(file, cb) {
  66. this.files_.add(file);
  67. this.loader_.load(file, (result) => {
  68. this.files_.delete(file);
  69. cb(result);
  70. if (this.files_.size == 0) {
  71. this.onLoad();
  72. }
  73. });
  74. }
  75. };
  76. class BasicCharacterControllerProxy {
  77. constructor(animations) {
  78. this.animations_ = animations;
  79. }
  80. get animations() {
  81. return this.animations_;
  82. }
  83. };
  84. class AnimatedMesh {
  85. constructor(params) {
  86. this.params_ = params;
  87. this.group_ = new THREE.Group();
  88. this.params_.scene.add(this.group_);
  89. this.target_ = null;
  90. this.animations_ = {};
  91. this.mixer_ = null;
  92. this.onLoad = () => {};
  93. this.Load_();
  94. }
  95. Destroy() {
  96. if (this.target_) {
  97. this.target_.traverse(c => {
  98. if (c.material) {
  99. c.material.dispose();
  100. }
  101. if (c.geometry) {
  102. c.geometry.dispose();
  103. }
  104. });
  105. }
  106. this.params_.scene.remove(this.group_);
  107. }
  108. get position() {
  109. return this.group_.position;
  110. }
  111. get quaternion() {
  112. return this.group_.quaternion;
  113. }
  114. Load_() {
  115. const modelData = _CHARACTER_MODELS[this.params_.desc.character.class];
  116. const loader = new FBXLoader();
  117. loader.setPath(modelData.path);
  118. loader.load(modelData.base, (fbx) => {
  119. fbx.scale.setScalar(0.1);
  120. fbx.traverse(c => {
  121. c.castShadow = true;
  122. });
  123. this.target_ = fbx;
  124. this.group_.add(this.target_);
  125. this.mixer_ = new THREE.AnimationMixer(this.target_);
  126. const _OnLoad = (animName, anim) => {
  127. const clip = anim.animations[0];
  128. const action = this.mixer_.clipAction(clip);
  129. this.animations_[animName] = {
  130. clip: clip,
  131. action: action,
  132. };
  133. };
  134. // LoadingManager seems to be broken when you attempt to load multiple
  135. // resources multiple times, only first onLoad is called.
  136. // So roll our own.
  137. const loader = new FBXLoader();
  138. loader.setPath(modelData.path);
  139. this.manager_ = new OurLoadingManager(loader);
  140. this.manager_.load(
  141. modelData.animations.idle,
  142. (a) => { _OnLoad('idle', a); });
  143. this.manager_.load(
  144. modelData.animations.walk,
  145. (a) => { _OnLoad('walk', a); });
  146. this.manager_.load(
  147. modelData.animations.run,
  148. (a) => { _OnLoad('run', a); });
  149. this.manager_.load(
  150. modelData.animations.dance,
  151. (a) => { _OnLoad('dance', a); });
  152. this.manager_.onLoad = () => {
  153. this.onLoad();
  154. };
  155. });
  156. }
  157. Update(timeElapsed) {
  158. if (this.mixer_) {
  159. this.mixer_.update(timeElapsed);
  160. }
  161. }
  162. };
  163. class BasicCharacterController {
  164. constructor(params) {
  165. this._Init(params);
  166. }
  167. _Init(params) {
  168. this.params_ = params;
  169. this.decceleration_ = new THREE.Vector3(-0.0005, -0.0001, -5.0);
  170. this.acceleration_ = new THREE.Vector3(1, 0.25, 50.0);
  171. this.velocity_ = new THREE.Vector3(0, 0, 0);
  172. this.position_ = new THREE.Vector3();
  173. this.quaternion_ = new THREE.Quaternion();
  174. this.loaded_ = false;
  175. this._input = new BasicCharacterControllerInput();
  176. this.target_ = new AnimatedMesh({
  177. scene: params.scene,
  178. desc: params.desc,
  179. });
  180. this.target_.onLoad = () => {
  181. this.loaded_ = true;
  182. this.stateMachine_.SetState('idle');
  183. }
  184. this.stateMachine_ = new CharacterFSM(
  185. new BasicCharacterControllerProxy(this.target_.animations_));
  186. }
  187. get IsLoaded() {
  188. return this.loaded_;
  189. }
  190. get Position() {
  191. return this.position_;
  192. }
  193. get Rotation() {
  194. return this.quaternion_;
  195. }
  196. SetTransform(p, q) {
  197. this.position_.copy(p);
  198. this.quaternion_.copy(q);
  199. this.target_.group_.position.copy(this.position_);
  200. this.target_.group_.quaternion.copy(this.quaternion_);
  201. }
  202. CreateTransformPacket() {
  203. return [
  204. this.stateMachine_.currentState_.Name,
  205. this.position_.toArray(),
  206. this.quaternion_.toArray(),
  207. ];
  208. }
  209. Update(timeInSeconds) {
  210. if (!this.stateMachine_.currentState_) {
  211. return;
  212. }
  213. this.stateMachine_.Update(timeInSeconds, this._input);
  214. const velocity = this.velocity_;
  215. const frameDecceleration = new THREE.Vector3(
  216. velocity.x * this.decceleration_.x,
  217. velocity.y * this.decceleration_.y,
  218. velocity.z * this.decceleration_.z
  219. );
  220. frameDecceleration.multiplyScalar(timeInSeconds);
  221. frameDecceleration.z = Math.sign(frameDecceleration.z) * Math.min(
  222. Math.abs(frameDecceleration.z), Math.abs(velocity.z));
  223. velocity.add(frameDecceleration);
  224. const controlObject = this.target_;
  225. const _Q = new THREE.Quaternion();
  226. const _A = new THREE.Vector3();
  227. const _R = controlObject.quaternion.clone();
  228. const acc = this.acceleration_.clone();
  229. if (this._input._keys.shift) {
  230. acc.multiplyScalar(2.0);
  231. }
  232. if (this.stateMachine_.currentState_.Name == 'dance') {
  233. acc.multiplyScalar(0.0);
  234. }
  235. if (this._input._keys.forward) {
  236. velocity.z += acc.z * timeInSeconds;
  237. }
  238. if (this._input._keys.backward) {
  239. velocity.z -= acc.z * timeInSeconds;
  240. }
  241. if (this._input._keys.left) {
  242. _A.set(0, 1, 0);
  243. _Q.setFromAxisAngle(_A, 4.0 * Math.PI * timeInSeconds * this.acceleration_.y);
  244. _R.multiply(_Q);
  245. }
  246. if (this._input._keys.right) {
  247. _A.set(0, 1, 0);
  248. _Q.setFromAxisAngle(_A, 4.0 * -Math.PI * timeInSeconds * this.acceleration_.y);
  249. _R.multiply(_Q);
  250. }
  251. controlObject.quaternion.copy(_R);
  252. const oldPosition = new THREE.Vector3();
  253. oldPosition.copy(controlObject.position);
  254. const forward = new THREE.Vector3(0, 0, 1);
  255. forward.applyQuaternion(controlObject.quaternion);
  256. forward.normalize();
  257. const sideways = new THREE.Vector3(1, 0, 0);
  258. sideways.applyQuaternion(controlObject.quaternion);
  259. sideways.normalize();
  260. sideways.multiplyScalar(velocity.x * timeInSeconds);
  261. forward.multiplyScalar(velocity.z * timeInSeconds);
  262. controlObject.position.add(forward);
  263. controlObject.position.add(sideways);
  264. this.position_.copy(controlObject.position);
  265. this.quaternion_.copy(controlObject.quaternion);
  266. this.target_.Update(timeInSeconds);
  267. }
  268. };
  269. class BasicCharacterControllerInput {
  270. constructor() {
  271. this._Init();
  272. }
  273. _Init() {
  274. this._keys = {
  275. forward: false,
  276. backward: false,
  277. left: false,
  278. right: false,
  279. space: false,
  280. shift: false,
  281. };
  282. document.addEventListener('keydown', (e) => this.OnKeyDown_(e), false);
  283. document.addEventListener('keyup', (e) => this._onKeyUp(e), false);
  284. }
  285. OnKeyDown_(event) {
  286. if (event.currentTarget.activeElement != document.body) {
  287. return;
  288. }
  289. switch (event.keyCode) {
  290. case 87: // w
  291. this._keys.forward = true;
  292. break;
  293. case 65: // a
  294. this._keys.left = true;
  295. break;
  296. case 83: // s
  297. this._keys.backward = true;
  298. break;
  299. case 68: // d
  300. this._keys.right = true;
  301. break;
  302. case 32: // SPACE
  303. this._keys.space = true;
  304. break;
  305. case 16: // SHIFT
  306. this._keys.shift = true;
  307. break;
  308. }
  309. }
  310. _onKeyUp(event) {
  311. if (event.currentTarget.activeElement != document.body) {
  312. return;
  313. }
  314. switch(event.keyCode) {
  315. case 87: // w
  316. this._keys.forward = false;
  317. break;
  318. case 65: // a
  319. this._keys.left = false;
  320. break;
  321. case 83: // s
  322. this._keys.backward = false;
  323. break;
  324. case 68: // d
  325. this._keys.right = false;
  326. break;
  327. case 32: // SPACE
  328. this._keys.space = false;
  329. break;
  330. case 16: // SHIFT
  331. this._keys.shift = false;
  332. break;
  333. }
  334. }
  335. };
  336. class FiniteStateMachine {
  337. constructor() {
  338. this._states = {};
  339. this.currentState_ = null;
  340. }
  341. _AddState(name, type) {
  342. this._states[name] = type;
  343. }
  344. SetState(name) {
  345. const prevState = this.currentState_;
  346. if (prevState) {
  347. if (prevState.Name == name) {
  348. return;
  349. }
  350. prevState.Exit();
  351. }
  352. const state = new this._states[name](this);
  353. this.currentState_ = state;
  354. state.Enter(prevState);
  355. }
  356. Update(timeElapsed, input) {
  357. if (this.currentState_) {
  358. this.currentState_.Update(timeElapsed, input);
  359. }
  360. }
  361. };
  362. class CharacterFSM extends FiniteStateMachine {
  363. constructor(proxy) {
  364. super();
  365. this._proxy = proxy;
  366. this._Init();
  367. }
  368. _Init() {
  369. this._AddState('idle', IdleState);
  370. this._AddState('walk', WalkState);
  371. this._AddState('run', RunState);
  372. this._AddState('dance', DanceState);
  373. }
  374. };
  375. class State {
  376. constructor(parent) {
  377. this._parent = parent;
  378. }
  379. Enter() {}
  380. Exit() {}
  381. Update() {}
  382. };
  383. class DanceState extends State {
  384. constructor(parent) {
  385. super(parent);
  386. this._FinishedCallback = () => {
  387. this._Finished();
  388. }
  389. }
  390. get Name() {
  391. return 'dance';
  392. }
  393. Enter(prevState) {
  394. const curAction = this._parent._proxy.animations_['dance'].action;
  395. const mixer = curAction.getMixer();
  396. mixer.addEventListener('finished', this._FinishedCallback);
  397. if (prevState) {
  398. const prevAction = this._parent._proxy.animations_[prevState.Name].action;
  399. curAction.reset();
  400. curAction.setLoop(THREE.LoopOnce, 1);
  401. curAction.clampWhenFinished = true;
  402. curAction.crossFadeFrom(prevAction, 0.2, true);
  403. curAction.play();
  404. } else {
  405. curAction.play();
  406. }
  407. }
  408. _Finished() {
  409. this._Cleanup();
  410. this._parent.SetState('idle');
  411. }
  412. _Cleanup() {
  413. const action = this._parent._proxy.animations_['dance'].action;
  414. action.getMixer().removeEventListener('finished', this._CleanupCallback);
  415. }
  416. Exit() {
  417. this._Cleanup();
  418. }
  419. Update(_) {
  420. }
  421. };
  422. class WalkState extends State {
  423. constructor(parent) {
  424. super(parent);
  425. }
  426. get Name() {
  427. return 'walk';
  428. }
  429. Enter(prevState) {
  430. const curAction = this._parent._proxy.animations_['walk'].action;
  431. if (prevState) {
  432. const prevAction = this._parent._proxy.animations_[prevState.Name].action;
  433. curAction.enabled = true;
  434. if (prevState.Name == 'run') {
  435. const ratio = curAction.getClip().duration / prevAction.getClip().duration;
  436. curAction.time = prevAction.time * ratio;
  437. } else {
  438. curAction.time = 0.0;
  439. curAction.setEffectiveTimeScale(1.0);
  440. curAction.setEffectiveWeight(1.0);
  441. }
  442. curAction.crossFadeFrom(prevAction, 0.5, true);
  443. curAction.play();
  444. } else {
  445. curAction.play();
  446. }
  447. }
  448. Exit() {
  449. }
  450. Update(timeElapsed, input) {
  451. if (!input) {
  452. return;
  453. }
  454. if (input._keys.forward || input._keys.backward) {
  455. if (input._keys.shift) {
  456. this._parent.SetState('run');
  457. }
  458. return;
  459. }
  460. this._parent.SetState('idle');
  461. }
  462. };
  463. class RunState extends State {
  464. constructor(parent) {
  465. super(parent);
  466. }
  467. get Name() {
  468. return 'run';
  469. }
  470. Enter(prevState) {
  471. const curAction = this._parent._proxy.animations_['run'].action;
  472. if (prevState) {
  473. const prevAction = this._parent._proxy.animations_[prevState.Name].action;
  474. curAction.enabled = true;
  475. if (prevState.Name == 'walk') {
  476. const ratio = curAction.getClip().duration / prevAction.getClip().duration;
  477. curAction.time = prevAction.time * ratio;
  478. } else {
  479. curAction.time = 0.0;
  480. curAction.setEffectiveTimeScale(1.0);
  481. curAction.setEffectiveWeight(1.0);
  482. }
  483. curAction.crossFadeFrom(prevAction, 0.5, true);
  484. curAction.play();
  485. } else {
  486. curAction.play();
  487. }
  488. }
  489. Exit() {
  490. }
  491. Update(timeElapsed, input) {
  492. if (!input) {
  493. return;
  494. }
  495. if (input._keys.forward || input._keys.backward) {
  496. if (!input._keys.shift) {
  497. this._parent.SetState('walk');
  498. }
  499. return;
  500. }
  501. this._parent.SetState('idle');
  502. }
  503. };
  504. class IdleState extends State {
  505. constructor(parent) {
  506. super(parent);
  507. }
  508. get Name() {
  509. return 'idle';
  510. }
  511. Enter(prevState) {
  512. const idleAction = this._parent._proxy.animations_['idle'].action;
  513. if (prevState) {
  514. const prevAction = this._parent._proxy.animations_[prevState.Name].action;
  515. idleAction.time = 0.0;
  516. idleAction.enabled = true;
  517. idleAction.setEffectiveTimeScale(1.0);
  518. idleAction.setEffectiveWeight(1.0);
  519. idleAction.crossFadeFrom(prevAction, 0.5, true);
  520. idleAction.play();
  521. } else {
  522. idleAction.play();
  523. }
  524. }
  525. Exit() {
  526. }
  527. Update(_, input) {
  528. if (!input) {
  529. return;
  530. }
  531. if (input._keys.forward || input._keys.backward) {
  532. this._parent.SetState('walk');
  533. } else if (input._keys.space) {
  534. this._parent.SetState('dance');
  535. }
  536. }
  537. };
  538. class ThirdPersonCamera {
  539. constructor(params) {
  540. this.params_ = params;
  541. this._camera = params.camera;
  542. this._currentPosition = new THREE.Vector3();
  543. this._currentLookat = new THREE.Vector3();
  544. }
  545. _CalculateIdealOffset() {
  546. const idealOffset = new THREE.Vector3(-15, 20, -30);
  547. idealOffset.applyQuaternion(this.params_.target.Rotation);
  548. idealOffset.add(this.params_.target.Position);
  549. return idealOffset;
  550. }
  551. _CalculateIdealLookat() {
  552. const idealLookat = new THREE.Vector3(0, 10, 50);
  553. idealLookat.applyQuaternion(this.params_.target.Rotation);
  554. idealLookat.add(this.params_.target.Position);
  555. return idealLookat;
  556. }
  557. Update(timeElapsed) {
  558. const idealOffset = this._CalculateIdealOffset();
  559. const idealLookat = this._CalculateIdealLookat();
  560. // const t = 0.05;
  561. // const t = 4.0 * timeElapsed;
  562. const t = 1.0 - Math.pow(0.001, timeElapsed);
  563. this._currentPosition.lerp(idealOffset, t);
  564. this._currentLookat.lerp(idealLookat, t);
  565. this._camera.position.copy(this._currentPosition);
  566. this._camera.lookAt(this._currentLookat);
  567. }
  568. }
  569. class PlayerEntity {
  570. constructor(params) {
  571. this.params_ = params;
  572. this.Init_();
  573. }
  574. Init_() {
  575. }
  576. CreateFromDesc(desc) {
  577. const params = {
  578. camera: this.params_.camera,
  579. scene: this.params_.scene,
  580. desc: desc,
  581. };
  582. this.controls_ = new BasicCharacterController(params);
  583. this.thirdPersonCamera_ = new ThirdPersonCamera({
  584. camera: this.params_.camera,
  585. target: this.controls_,
  586. });
  587. this.updateTimer_ = 0.0;
  588. }
  589. UpdateTransform(data) {
  590. const s = data[0];
  591. const p = data[1];
  592. const q = data[2];
  593. this.controls_.SetTransform(
  594. new THREE.Vector3(...p),
  595. new THREE.Quaternion(...q)
  596. );
  597. }
  598. Update(timeElapsed) {
  599. this.controls_.Update(timeElapsed);
  600. this.thirdPersonCamera_.Update(timeElapsed);
  601. this.SendTransform_(timeElapsed);
  602. }
  603. SendTransform_(timeElapsed) {
  604. this.updateTimer_ -= timeElapsed;
  605. if (this.updateTimer_ <= 0.0 && this.controls_.IsLoaded) {
  606. this.updateTimer_ = 0.1;
  607. this.params_.socket.emit(
  608. 'world.update',
  609. this.controls_.CreateTransformPacket(),
  610. );
  611. }
  612. }
  613. };
  614. class NetworkCharacterController {
  615. constructor(params) {
  616. this._Init(params);
  617. }
  618. Destroy() {
  619. this.name_.Destroy();
  620. this.name_ = null;
  621. this.target_.Destroy();
  622. this.target_ = null;
  623. }
  624. _Init(params) {
  625. this.params_ = params;
  626. this.target_ = new AnimatedMesh({
  627. scene: params.scene,
  628. desc: params.desc,
  629. });
  630. this.target_.onLoad = () => {
  631. this.stateMachine_ = new CharacterFSM(
  632. new BasicCharacterControllerProxy(this.target_.animations_));
  633. this.stateMachine_.SetState('idle');
  634. }
  635. this.name_ = new FloatingName({
  636. parent: this.target_.group_,
  637. desc: params.desc,
  638. });
  639. }
  640. get position() {
  641. return this.target_.position;
  642. }
  643. get quaternion() {
  644. return this.target_.quaternion;
  645. }
  646. SetState(s) {
  647. if (!this.stateMachine_) {
  648. return;
  649. }
  650. this.stateMachine_.SetState(s);
  651. }
  652. Update(timeInSeconds) {
  653. if (!this.stateMachine_) {
  654. return;
  655. }
  656. this.stateMachine_.Update(timeInSeconds, null);
  657. this.target_.Update(timeInSeconds);
  658. }
  659. };
  660. class NetworkEntity {
  661. constructor(params) {
  662. this.params_ = params;
  663. this.transformUpdates_ = [];
  664. this.targetFrame_ = null;
  665. this.lastFrame_ = null;
  666. this.Init_();
  667. }
  668. Destroy() {
  669. this.controller_.Destroy();
  670. }
  671. Init_() {
  672. }
  673. CreateFromDesc(desc, transform) {
  674. this.controller_ = new NetworkCharacterController({
  675. scene: this.params_.scene,
  676. desc: desc,
  677. });
  678. this.controller_.position.set(...transform[1]);
  679. this.controller_.quaternion.set(...transform[2]);
  680. this.targetFrame_ = {time: 0.1, transform: transform};
  681. }
  682. UpdateTransform(data) {
  683. this.transformUpdates_.push({time: 0.1, transform: data});
  684. }
  685. Update(timeElapsed) {
  686. this.controller_.Update(timeElapsed);
  687. this.ApplyLCT_(timeElapsed);
  688. }
  689. ApplyLCT_(timeElapsed) {
  690. if (this.transformUpdates_.length == 0) {
  691. return;
  692. }
  693. for (let i = 0; i < this.transformUpdates_.length; ++i) {
  694. this.transformUpdates_[i].time -= timeElapsed;
  695. }
  696. while (this.transformUpdates_.length > 0 &&
  697. this.transformUpdates_[0].time <= 0.0) {
  698. this.lastFrame_ = {
  699. transform: [
  700. this.targetFrame_.transform[0],
  701. this.controller_.position.toArray(),
  702. this.controller_.quaternion.toArray()
  703. ]
  704. };
  705. this.targetFrame_ = this.transformUpdates_.shift();
  706. this.targetFrame_.time = 0.0;
  707. }
  708. if (this.targetFrame_ && this.lastFrame_) {
  709. this.targetFrame_.time += timeElapsed;
  710. const p1 = new THREE.Vector3(...this.lastFrame_.transform[1]);
  711. const p2 = new THREE.Vector3(...this.targetFrame_.transform[1]);
  712. const q1 = new THREE.Quaternion(...this.lastFrame_.transform[2]);
  713. const q2 = new THREE.Quaternion(...this.targetFrame_.transform[2]);
  714. this.controller_.position.copy(p1);
  715. this.controller_.quaternion.copy(q1);
  716. const t = Math.max(Math.min(this.targetFrame_.time / 0.1, 1.0), 0.0);
  717. this.controller_.position.lerp(p2, t);
  718. this.controller_.quaternion.slerp(q2, t);
  719. this.controller_.SetState(this.lastFrame_.transform[0]);
  720. }
  721. }
  722. }
  723. class Chatbox {
  724. constructor(params) {
  725. this.params_ = params;
  726. this.OnChat = () => {};
  727. this.Init_();
  728. }
  729. Init_() {
  730. this.element_ = document.getElementById('chat-input');
  731. this.element_.addEventListener(
  732. 'keydown', (e) => this.OnKeyDown_(e), false);
  733. }
  734. OnKeyDown_(evt) {
  735. if (evt.keyCode === 13) {
  736. evt.preventDefault();
  737. const msg = this.element_.value;
  738. if (msg != '') {
  739. this.OnChat(msg);
  740. }
  741. this.element_.value = '';
  742. }
  743. }
  744. AddMessage(msg) {
  745. const e = document.createElement('div');
  746. e.className = 'chat-text';
  747. e.innerText = '[' + msg.name + ']: ' + msg.text;
  748. const chatElement = document.getElementById('chat-ui-text-area');
  749. chatElement.insertBefore(e, document.getElementById('chat-input'));
  750. }
  751. };
  752. class BasicMMODemo {
  753. constructor() {
  754. this._Initialize();
  755. }
  756. _Initialize() {
  757. this.threejs_ = new THREE.WebGLRenderer({
  758. antialias: true,
  759. });
  760. this.threejs_.outputEncoding = THREE.sRGBEncoding;
  761. this.threejs_.gammaFactor = 2.2;
  762. this.threejs_.shadowMap.enabled = true;
  763. this.threejs_.shadowMap.type = THREE.PCFSoftShadowMap;
  764. this.threejs_.setPixelRatio(window.devicePixelRatio);
  765. this.threejs_.setSize(window.innerWidth, window.innerHeight);
  766. document.getElementById('container').appendChild(
  767. this.threejs_.domElement);
  768. window.addEventListener('resize', () => {
  769. this.OnWindowResize_();
  770. }, false);
  771. const fov = 60;
  772. const aspect = 1920 / 1080;
  773. const near = 1.0;
  774. const far = 1000.0;
  775. this.camera_ = new THREE.PerspectiveCamera(fov, aspect, near, far);
  776. this.camera_.position.set(75, 20, 0);
  777. this.scene_ = new THREE.Scene();
  778. let light = new THREE.DirectionalLight(0xFFFFFF, 1.0);
  779. light.position.set(20, 100, 10);
  780. light.target.position.set(0, 0, 0);
  781. light.castShadow = true;
  782. light.shadow.bias = -0.001;
  783. light.shadow.mapSize.width = 2048;
  784. light.shadow.mapSize.height = 2048;
  785. light.shadow.camera.near = 0.1;
  786. light.shadow.camera.far = 500.0;
  787. light.shadow.camera.near = 0.5;
  788. light.shadow.camera.far = 500.0;
  789. light.shadow.camera.left = 100;
  790. light.shadow.camera.right = -100;
  791. light.shadow.camera.top = 100;
  792. light.shadow.camera.bottom = -100;
  793. this.scene_.add(light);
  794. light = new THREE.AmbientLight(0x101010);
  795. this.scene_.add(light);
  796. const controls = new OrbitControls(
  797. this.camera_, this.threejs_.domElement);
  798. controls.target.set(0, 20, 0);
  799. controls.update();
  800. const loader = new THREE.CubeTextureLoader();
  801. const texture = loader.load([
  802. './resources/posx.jpg',
  803. './resources/negx.jpg',
  804. './resources/posy.jpg',
  805. './resources/negy.jpg',
  806. './resources/posz.jpg',
  807. './resources/negz.jpg',
  808. ]);
  809. texture.encoding = THREE.sRGBEncoding;
  810. this.scene_.background = texture;
  811. const plane = new THREE.Mesh(
  812. new THREE.PlaneGeometry(100, 100, 10, 10),
  813. new THREE.MeshStandardMaterial({
  814. color: 0x808080,
  815. }));
  816. plane.castShadow = false;
  817. plane.receiveShadow = true;
  818. plane.rotation.x = -Math.PI / 2;
  819. this.scene_.add(plane);
  820. this.SetupSocket_();
  821. this.entities_ = {};
  822. this.chatbox_ = new Chatbox();
  823. this.chatbox_.OnChat = (txt) => { this.OnChat_(txt); };
  824. this.previousRAF_ = null;
  825. this.RAF_();
  826. }
  827. GenerateRandomName_() {
  828. const names1 = [
  829. 'Aspiring', 'Nameless', 'Cautionary', 'Excited',
  830. 'Modest', 'Maniacal', 'Caffeinated', 'Sleepy',
  831. 'Passionate', 'Masochistic', 'Aging', 'Pedantic',
  832. 'Talkative',
  833. ];
  834. const names2 = [
  835. 'Coder', 'Mute', 'Giraffe', 'Snowman',
  836. 'Machinist', 'Fondler', 'Typist',
  837. 'Noodler', 'Arborist', 'Peeper', 'Ghost',
  838. ];
  839. const n1 = names1[
  840. Math.floor(Math.random() * names1.length)];
  841. const n2 = names2[
  842. Math.floor(Math.random() * names2.length)];
  843. return n1 + ' ' + n2;
  844. }
  845. SetupSocket_() {
  846. this.socket_ = io('ws://localhost:3000', {
  847. reconnection: false,
  848. transports: ['websocket'],
  849. });
  850. this.socket_.on("connect", () => {
  851. console.log(this.socket_.id);
  852. const randomName = this.GenerateRandomName_();
  853. this.socket_.emit('login.commit', randomName);
  854. });
  855. this.socket_.on("disconnect", () => {
  856. console.log('DISCONNECTED: ' + this.socket_.id); // undefined
  857. });
  858. this.socket_.onAny((e, d) => {
  859. this.OnMessage_(e, d);
  860. });
  861. }
  862. OnChat_(txt) {
  863. this.socket_.emit('chat.msg', txt);
  864. }
  865. OnMessage_(e, d) {
  866. if (e == 'world.player') {
  867. this.playerID_ = d.id;
  868. const e = new PlayerEntity({
  869. scene: this.scene_,
  870. camera: this.camera_,
  871. socket: this.socket_
  872. });
  873. e.CreateFromDesc(d.desc);
  874. e.UpdateTransform(d.transform);
  875. this.entities_[d.id] = e;
  876. console.log('entering world: ' + d.id);
  877. } else if (e == 'world.update') {
  878. const updates = d;
  879. const alive = {};
  880. alive[this.playerID_] = this.entities_[this.playerID_];
  881. for (let u of updates) {
  882. if ('desc' in u) {
  883. const e = new NetworkEntity({scene: this.scene_});
  884. e.CreateFromDesc(u.desc, u.transform);
  885. this.entities_[u.id] = e;
  886. } else {
  887. this.entities_[u.id].UpdateTransform(u.transform);
  888. }
  889. alive[u.id] = this.entities_[u.id];
  890. }
  891. const dead = [];
  892. for (let k in this.entities_) {
  893. if (!(k in alive)) {
  894. dead.push(this.entities_[k]);
  895. }
  896. }
  897. this.entities_ = alive;
  898. for (let i = 0; i < dead.length; ++i) {
  899. dead[i].Destroy();
  900. }
  901. } else if (e == 'chat.message') {
  902. this.chatbox_.AddMessage(d);
  903. }
  904. }
  905. OnWindowResize_() {
  906. this.camera_.aspect = window.innerWidth / window.innerHeight;
  907. this.camera_.updateProjectionMatrix();
  908. this.threejs_.setSize(window.innerWidth, window.innerHeight);
  909. }
  910. RAF_() {
  911. requestAnimationFrame((t) => {
  912. if (this.previousRAF_ == null) {
  913. this.previousRAF_ = t;
  914. }
  915. this.Update_((t - this.previousRAF_) * 0.001);
  916. this.threejs_.render(this.scene_, this.camera_);
  917. this.previousRAF_ = t;
  918. this.RAF_();
  919. });
  920. }
  921. Update_(timeElapsed) {
  922. for (let k in this.entities_) {
  923. this.entities_[k].Update(timeElapsed);
  924. }
  925. }
  926. }
  927. let _APP = null;
  928. window.addEventListener('DOMContentLoaded', () => {
  929. _APP = new BasicMMODemo();
  930. });