main.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. import * as THREE from 'https://cdn.jsdelivr.net/npm/[email protected]/build/three.module.js';
  2. import {ColladaLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/ColladaLoader.js';
  3. import {FBXLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/FBXLoader.js';
  4. import {GLTFLoader} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/loaders/GLTFLoader.js';
  5. import {GUI} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/libs/dat.gui.module.js';
  6. import {BufferGeometryUtils} from 'https://cdn.jsdelivr.net/npm/[email protected]/examples/jsm/utils/BufferGeometryUtils.js';
  7. import {agent} from './agent.js';
  8. import {controls} from './controls.js';
  9. import {game} from './game.js';
  10. import {math} from './math.js';
  11. import {terrain} from './terrain.js';
  12. import {visibility} from './visibility.js';
  13. import {particles} from './particles.js';
  14. import {blaster} from './blaster.js';
  15. let _APP = null;
  16. const _NUM_BOIDS = 100;
  17. const _BOID_SPEED = 100;
  18. const _BOID_ACCELERATION = _BOID_SPEED / 2.5;
  19. const _BOID_FORCE_MAX = _BOID_ACCELERATION / 20.0;
  20. const _BOID_FORCE_ORIGIN = 50;
  21. const _BOID_FORCE_ALIGNMENT = 10;
  22. const _BOID_FORCE_SEPARATION = 20;
  23. const _BOID_FORCE_COLLISION = 50;
  24. const _BOID_FORCE_COHESION = 5;
  25. const _BOID_FORCE_WANDER = 3;
  26. class PlayerEntity {
  27. constructor(params) {
  28. this._model = params.model;
  29. this._params = params;
  30. this._game = params.game;
  31. this._fireCooldown = 0.0;
  32. this._velocity = new THREE.Vector3(0, 0, 0);
  33. this._direction = new THREE.Vector3(0, 0, -1);
  34. this._health = 1000.0;
  35. const x = 2.75;
  36. const y1 = 1.5;
  37. const y2 = 0.4;
  38. const z = 4.0;
  39. this._offsets = [
  40. new THREE.Vector3(-x, y1, -z),
  41. new THREE.Vector3(x, y1, -z),
  42. new THREE.Vector3(-x, -y2, -z),
  43. new THREE.Vector3(x, -y2, -z),
  44. ];
  45. this._offsetIndex = 0;
  46. this._visibilityIndex = this._game._visibilityGrid.UpdateItem(
  47. this._model.uuid, this);
  48. }
  49. get Enemy() {
  50. return false;
  51. }
  52. get Velocity() {
  53. return this._velocity;
  54. }
  55. get Direction() {
  56. return this._direction;
  57. }
  58. get Position() {
  59. return this._model.position;
  60. }
  61. get Radius() {
  62. return 1.0;
  63. }
  64. get Health() {
  65. return this._health;
  66. }
  67. get Dead() {
  68. return (this._health <= 0.0);
  69. }
  70. TakeDamage(dmg) {
  71. this._params.game._entities['_explosionSystem'].Splode(this.Position);
  72. this._health -= dmg;
  73. if (this._health <= 0.0) {
  74. this._game._visibilityGrid.RemoveItem(this._model.uuid, this._game._visibilityIndex);
  75. }
  76. }
  77. Fire() {
  78. if (this._fireCooldown > 0.0) {
  79. return;
  80. }
  81. this._fireCooldown = 0.05;
  82. const p = this._params.game._entities['_blasterSystem'].CreateParticle();
  83. p.Start = this._offsets[this._offsetIndex].clone();
  84. p.Start.applyQuaternion(this._model.quaternion);
  85. p.Start.add(this.Position);
  86. p.End = p.Start.clone();
  87. p.Velocity = this.Direction.clone().multiplyScalar(500.0);
  88. p.Length = 50.0;
  89. p.Colours = [
  90. new THREE.Color(4.0, 0.5, 0.5), new THREE.Color(0.0, 0.0, 0.0)];
  91. p.Life = 2.0;
  92. p.TotalLife = 2.0;
  93. p.Width = 0.25;
  94. this._offsetIndex = (this._offsetIndex + 1) % this._offsets.length;
  95. }
  96. Update(timeInSeconds) {
  97. if (this.Dead) {
  98. return;
  99. }
  100. this._visibilityIndex = this._game._visibilityGrid.UpdateItem(
  101. this._model.uuid, this, this._visibilityIndex);
  102. this._fireCooldown -= timeInSeconds;
  103. this._burstCooldown = Math.max(this._burstCooldown, 0.0);
  104. this._direction.copy(this._velocity);
  105. this._direction.normalize();
  106. this._direction.applyQuaternion(this._model.quaternion);
  107. }
  108. }
  109. class ExplodeParticles {
  110. constructor(game) {
  111. this._particleSystem = new particles.ParticleSystem(
  112. game, {texture: "./resources/explosion.png"});
  113. this._particles = [];
  114. }
  115. Splode(origin) {
  116. for (let i = 0; i < 96; i++) {
  117. const p = this._particleSystem.CreateParticle();
  118. p.Position.copy(origin);
  119. p.Velocity = new THREE.Vector3(
  120. math.rand_range(-1, 1),
  121. math.rand_range(-1, 1),
  122. math.rand_range(-1, 1)
  123. );
  124. p.Velocity.normalize();
  125. p.Velocity.multiplyScalar(50);
  126. p.TotalLife = 2.0;
  127. p.Life = p.TotalLife;
  128. p.Colours = [new THREE.Color(0xFF8010), new THREE.Color(0xFF8010)];
  129. p.Sizes = [4, 16];
  130. p.Size = p.Sizes[0];
  131. this._particles.push(p);
  132. }
  133. }
  134. Update(timeInSeconds) {
  135. const _V = new THREE.Vector3();
  136. this._particles = this._particles.filter(p => {
  137. return p.Alive;
  138. });
  139. for (const p of this._particles) {
  140. p.Life -= timeInSeconds;
  141. if (p.Life <= 0) {
  142. p.Alive = false;
  143. }
  144. p.Position.add(p.Velocity.clone().multiplyScalar(timeInSeconds));
  145. _V.copy(p.Velocity);
  146. _V.multiplyScalar(10.0 * timeInSeconds);
  147. const velocityLength = p.Velocity.length();
  148. if (_V.length() > velocityLength) {
  149. _V.normalize();
  150. _V.multiplyScalar(velocityLength)
  151. }
  152. p.Velocity.sub(_V);
  153. p.Size = math.lerp(p.Life / p.TotalLife, p.Sizes[0], p.Sizes[1]);
  154. p.Colour.copy(p.Colours[0]);
  155. p.Colour.lerp(p.Colours[1], 1.0 - p.Life / p.TotalLife);
  156. p.Opacity = math.smootherstep(p.Life / p.TotalLife, 0.0, 1.0);
  157. }
  158. this._particleSystem.Update();
  159. }
  160. };
  161. class ProceduralTerrain_Demo extends game.Game {
  162. constructor() {
  163. super();
  164. }
  165. _OnInitialize() {
  166. this._CreateGUI();
  167. this._userCamera = new THREE.Object3D();
  168. this._userCamera.position.set(4100, 0, 0);
  169. this._graphics.Camera.position.set(10340, 880, -2130);
  170. this._graphics.Camera.quaternion.set(-0.032, 0.885, 0.062, 0.46);
  171. this._score = 0;
  172. // This is 2D but eh, whatever.
  173. this._visibilityGrid = new visibility.VisibilityGrid(
  174. [new THREE.Vector3(-10000, 0, -10000), new THREE.Vector3(10000, 0, 10000)],
  175. [100, 100]);
  176. this._entities['_explosionSystem'] = new ExplodeParticles(this);
  177. this._entities['_blasterSystem'] = new blaster.BlasterSystem(
  178. {
  179. game: this,
  180. texture: "./resources/blaster.jpg",
  181. visibility: this._visibilityGrid,
  182. });
  183. this._entities['_terrain'] = new terrain.TerrainChunkManager({
  184. camera: this._graphics.Camera,
  185. scene: this._graphics.Scene,
  186. gui: this._gui,
  187. guiParams: this._guiParams,
  188. game: this
  189. });
  190. this._library = {};
  191. let loader = new GLTFLoader();
  192. loader.setPath('./resources/models/x-wing/');
  193. loader.load('scene.gltf', (gltf) => {
  194. const model = gltf.scene.children[0];
  195. model.scale.setScalar(0.5);
  196. const group = new THREE.Group();
  197. group.add(model);
  198. this._graphics.Scene.add(group);
  199. this._entities['player'] = new PlayerEntity(
  200. {model: group, camera: this._graphics.Camera, game: this});
  201. this._entities['_controls'] = new controls.ShipControls({
  202. target: this._entities['player'],
  203. camera: this._graphics.Camera,
  204. scene: this._graphics.Scene,
  205. domElement: this._graphics._threejs.domElement,
  206. gui: this._gui,
  207. guiParams: this._guiParams,
  208. });
  209. });
  210. loader = new GLTFLoader();
  211. loader.setPath('./resources/models/tie-fighter-gltf/');
  212. loader.load('scene.gltf', (obj) => {
  213. // This is bad, but I only want the mesh and I know this only has 1.
  214. // This is what you get when you don't have an art pipeline and don't feel like making one.
  215. obj.scene.traverse((c) => {
  216. if (c.isMesh) {
  217. const model = obj.scene.children[0];
  218. model.scale.setScalar(0.05);
  219. model.rotateX(Math.PI);
  220. const mat = new THREE.MeshStandardMaterial({
  221. map: new THREE.TextureLoader().load(
  222. './resources/models/tie-fighter-gltf/textures/hullblue_baseColor.png'),
  223. normalMap: new THREE.TextureLoader().load(
  224. './resources/models/tie-fighter-gltf/textures/hullblue_normal.png'),
  225. });
  226. model.material = mat;
  227. this._library['tie-fighter'] = model;
  228. }
  229. if (this._library['tie-fighter']) {
  230. this._CreateEnemyShips();
  231. }
  232. });
  233. });
  234. this._LoadBackground();
  235. }
  236. _CreateEnemyShips() {
  237. const positions = [
  238. new THREE.Vector3(8000, 0, 0),
  239. new THREE.Vector3(-7000, 50, -100),
  240. ];
  241. const colours = [
  242. new THREE.Color(4.0, 0.5, 0.5),
  243. new THREE.Color(0.5, 0.5, 4.0),
  244. ];
  245. for (let j = 0; j < 2; j++) {
  246. const p = positions[j];
  247. let loader = new GLTFLoader();
  248. loader.setPath('./resources/models/star-destroyer/');
  249. loader.load('scene.gltf', (gltf) => {
  250. const model = gltf.scene.children[0];
  251. model.scale.setScalar(20.0);
  252. model.rotateZ(Math.PI / 2.0);
  253. const cruiser = model;
  254. cruiser.position.set(p.x, p.y, p.z);
  255. cruiser.castShadow = true;
  256. cruiser.receiveShadow = true;
  257. cruiser.updateWorldMatrix();
  258. this._graphics.Scene.add(cruiser);
  259. });
  260. for (let i = 0; i < _NUM_BOIDS; i++) {
  261. let params = {
  262. mesh: this._library['tie-fighter'].clone(),
  263. speedMin: 1.0,
  264. speedMax: 1.0,
  265. speed: _BOID_SPEED,
  266. maxSteeringForce: _BOID_FORCE_MAX,
  267. acceleration: _BOID_ACCELERATION,
  268. seekGoal: p,
  269. colour: colours[j],
  270. };
  271. const e = new agent.Agent(this, params);
  272. this._entities['_boid_' + i] = e;
  273. }
  274. break;
  275. }
  276. }
  277. EnemyDied() {
  278. this._score++;
  279. document.getElementById('scoreText').innerText = this._score;
  280. }
  281. _CreateGUI() {
  282. this._CreateGameGUI();
  283. this._CreateControlGUI();
  284. }
  285. _CreateGameGUI() {
  286. const guiDiv = document.createElement('div');
  287. guiDiv.className = 'guiRoot guiBox';
  288. const scoreDiv = document.createElement('div');
  289. scoreDiv.className = 'vertical';
  290. const scoreTitle = document.createElement('div');
  291. scoreTitle.className = 'guiBigText';
  292. scoreTitle.innerText = 'KILLS';
  293. const scoreText = document.createElement('div');
  294. scoreText.className = 'guiSmallText';
  295. scoreText.innerText = '0';
  296. scoreText.id = 'scoreText';
  297. scoreDiv.appendChild(scoreTitle);
  298. scoreDiv.appendChild(scoreText);
  299. guiDiv.appendChild(scoreDiv);
  300. document.body.appendChild(guiDiv);
  301. }
  302. _CreateControlGUI() {
  303. this._guiParams = {
  304. general: {
  305. },
  306. };
  307. this._gui = new GUI();
  308. this._gui.hide();
  309. const generalRollup = this._gui.addFolder('General');
  310. this._gui.close();
  311. }
  312. _LoadBackground() {
  313. this._graphics.Scene.background = new THREE.Color(0xFFFFFF);
  314. const loader = new THREE.CubeTextureLoader();
  315. const texture = loader.load([
  316. './resources/space-posx.jpg',
  317. './resources/space-negx.jpg',
  318. './resources/space-posy.jpg',
  319. './resources/space-negy.jpg',
  320. './resources/space-posz.jpg',
  321. './resources/space-negz.jpg',
  322. ]);
  323. this._graphics._scene.background = texture;
  324. }
  325. _OnStep(timeInSeconds) {
  326. }
  327. }
  328. function _Main() {
  329. _APP = new ProceduralTerrain_Demo();
  330. }
  331. _Main();