initial commit
This commit is contained in:
		
						commit
						b2a0e586b2
					
				
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,4 @@
 | 
			
		||||
/dist
 | 
			
		||||
/node_modules
 | 
			
		||||
/.idea
 | 
			
		||||
package-lock.json
 | 
			
		||||
							
								
								
									
										11
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
<!DOCTYPE html>
 | 
			
		||||
<html lang="en">
 | 
			
		||||
<head>
 | 
			
		||||
    <meta charset="UTF-8">
 | 
			
		||||
    <title>Troonia</title>
 | 
			
		||||
    <style>html, body {margin: 0; padding: 0; width: 100%; height: 100%;}</style>
 | 
			
		||||
</head>
 | 
			
		||||
<body>
 | 
			
		||||
<script src="bundle.js" type="module"></script>
 | 
			
		||||
</body>
 | 
			
		||||
</html>
 | 
			
		||||
							
								
								
									
										37
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								package.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,37 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "troonia",
 | 
			
		||||
  "version": "1.0.0",
 | 
			
		||||
  "author": "vance",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "scripts": {
 | 
			
		||||
    "build": "concurrently \"tsc -p ./src/server\" \"webpack --config ./webpack.prod.cjs\"",
 | 
			
		||||
    "dev": "concurrently -k \"tsc -p ./src/server -w\" \"nodemon ./dist/server/server/main.js\" \"webpack serve --config ./webpack.dev.cjs\"",
 | 
			
		||||
    "test": "echo \"Error: no test specified\" && exit 1",
 | 
			
		||||
    "start": "node ./dist/server/server/main.js",
 | 
			
		||||
    "format": "find ./src -type f -exec clang-format -i -style=Google {} \\;"
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/dat.gui": "^0.7.7",
 | 
			
		||||
    "@types/express": "^4.17.14",
 | 
			
		||||
    "@types/node": "^17.0.45",
 | 
			
		||||
    "@types/three": "^0.144.0",
 | 
			
		||||
    "concurrently": "^7.5.0",
 | 
			
		||||
    "copy-webpack-plugin": "^11.0.0",
 | 
			
		||||
    "nodemon": "^2.0.20",
 | 
			
		||||
    "ts-loader": "^9.4.1",
 | 
			
		||||
    "typescript": "^4.8.4",
 | 
			
		||||
    "webpack": "^5.74.0",
 | 
			
		||||
    "webpack-cli": "^4.10.0",
 | 
			
		||||
    "webpack-dev-server": "^4.11.1",
 | 
			
		||||
    "webpack-merge": "^5.8.0"
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "cannon-es": "^0.20.0",
 | 
			
		||||
    "dat.gui": "^0.7.9",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "socket.io": "^4.5.3",
 | 
			
		||||
    "socket.io-client": "^4.5.3",
 | 
			
		||||
    "three": "^0.145.0",
 | 
			
		||||
    "@tweenjs/tween.js": "^18.6.4"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										20
									
								
								src/client/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								src/client/main.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,20 @@
 | 
			
		||||
import {Troonia} from './troonia';
 | 
			
		||||
import {get_viewport_size} from './util';
 | 
			
		||||
 | 
			
		||||
// noinspection SpellCheckingInspection
 | 
			
		||||
const troonia = new Troonia();
 | 
			
		||||
 | 
			
		||||
window.addEventListener('resize', resize, false);
 | 
			
		||||
 | 
			
		||||
function animate() {
 | 
			
		||||
  requestAnimationFrame(animate);
 | 
			
		||||
  troonia.update();
 | 
			
		||||
}
 | 
			
		||||
animate();
 | 
			
		||||
 | 
			
		||||
function resize() {
 | 
			
		||||
  const viewport = get_viewport_size();
 | 
			
		||||
  troonia.camera.aspect = viewport.width / viewport.height;
 | 
			
		||||
  troonia.camera.updateProjectionMatrix();
 | 
			
		||||
  troonia.renderer.setSize(viewport.width, viewport.height);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/client/physics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/client/physics.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import {BasePhysics, IPhysicsObject} from '../common/physics';
 | 
			
		||||
import type {World} from './world';
 | 
			
		||||
import * as TWEEN from '@tweenjs/tween.js';
 | 
			
		||||
import type {Quaternion} from 'three';
 | 
			
		||||
 | 
			
		||||
export class Physics extends BasePhysics {
 | 
			
		||||
  constructor(baseWorld: World) {
 | 
			
		||||
    super(baseWorld);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  override setPos(object: IPhysicsObject, body: IPhysicsObject) {
 | 
			
		||||
    new TWEEN.Tween(object.position).to(body.position, 50).start();
 | 
			
		||||
    new TWEEN.Tween(object.quaternion)
 | 
			
		||||
        .to(body.quaternion, 50)
 | 
			
		||||
        .onUpdate(() => (<Quaternion>object.quaternion).normalize())
 | 
			
		||||
        .start();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // @ts-ignore
 | 
			
		||||
  update(delta: number) {
 | 
			
		||||
    TWEEN.update();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										127
									
								
								src/client/troonia.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/client/troonia.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,127 @@
 | 
			
		||||
import type {IBaseTroonia, TrooniaState} from '../common/troonia';
 | 
			
		||||
import {io, Socket} from 'socket.io-client';
 | 
			
		||||
import {World} from './world';
 | 
			
		||||
import {get_viewport_size} from './util';
 | 
			
		||||
import {PerspectiveCamera, WebGLRenderer} from 'three';
 | 
			
		||||
import {GUI} from 'dat.gui';
 | 
			
		||||
import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls';
 | 
			
		||||
import {BaseTroonia} from '../common/troonia';
 | 
			
		||||
import {TestScene} from '../common/scenes';
 | 
			
		||||
import type {IPlayerData} from '../common/player';
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
export class Troonia extends BaseTroonia implements IBaseTroonia {
 | 
			
		||||
  socket: Socket;
 | 
			
		||||
  renderer: WebGLRenderer;
 | 
			
		||||
  camera: PerspectiveCamera;
 | 
			
		||||
  gui: GUI = new GUI();
 | 
			
		||||
  world: World;
 | 
			
		||||
 | 
			
		||||
  constructor() {
 | 
			
		||||
    super();
 | 
			
		||||
    this.socket = io();
 | 
			
		||||
 | 
			
		||||
    const viewport = get_viewport_size();
 | 
			
		||||
 | 
			
		||||
    this.renderer = new WebGLRenderer({antialias: true});
 | 
			
		||||
    this.renderer.setSize(viewport.width, viewport.height);
 | 
			
		||||
    document.body.appendChild(this.renderer.domElement);
 | 
			
		||||
 | 
			
		||||
    this.camera =
 | 
			
		||||
        new PerspectiveCamera(75, viewport.width / viewport.height, 0.1, 2000);
 | 
			
		||||
    this.camera.position.set(0, 50, 0);
 | 
			
		||||
    this.camera.lookAt(0, 0, 0);
 | 
			
		||||
 | 
			
		||||
    const controls = new OrbitControls(this.camera, this.renderer.domElement);
 | 
			
		||||
    controls.target.set(0, 1.25, 0);
 | 
			
		||||
    controls.update();
 | 
			
		||||
 | 
			
		||||
    this.socket.on('connect', () => {
 | 
			
		||||
      this.onConnect();
 | 
			
		||||
    });
 | 
			
		||||
    this.socket.on('disconnect', (message: any) => {
 | 
			
		||||
      this.onDisconnect(message);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.world = new World(this);
 | 
			
		||||
    this.world.load(TestScene);
 | 
			
		||||
 | 
			
		||||
    this.socket.on('syncState', (state: TrooniaState) => {
 | 
			
		||||
      this.world.physics.bodies = state.objects;
 | 
			
		||||
      this.world.physics.syncState();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.socket.on('syncPlayers', (players: {[id: string]: IPlayerData}) => {
 | 
			
		||||
      for (const player in players) {
 | 
			
		||||
        if (player !== this.socket.id) {
 | 
			
		||||
          this.world.addPlayer(player);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.socket.on('addPlayer', (id: string) => {
 | 
			
		||||
      if (id !== this.socket.id) {
 | 
			
		||||
        this.world.addPlayer(id);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.socket.on('removePlayer', (id: string) => {
 | 
			
		||||
      if (id !== this.socket.id) {
 | 
			
		||||
        this.world.removePlayer(id);
 | 
			
		||||
      }
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    document.addEventListener('keydown', (event: KeyboardEvent) => {
 | 
			
		||||
      switch (event.code) {
 | 
			
		||||
        case 'KeyW':
 | 
			
		||||
          this.world.player.vec3.z = 1;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'KeyA':
 | 
			
		||||
          this.world.player.vec3.x = 1;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'Space':
 | 
			
		||||
          this.world.player.vec3.y = 1;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'KeyS':
 | 
			
		||||
          this.world.player.vec3.z = -1;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'KeyD':
 | 
			
		||||
          this.world.player.vec3.x = -1;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      this.socket.emit('updatePlayer', this.world.player);
 | 
			
		||||
    }, false);
 | 
			
		||||
    document.addEventListener('keyup', (event: KeyboardEvent) => {
 | 
			
		||||
      switch (event.code) {
 | 
			
		||||
        case 'KeyW':
 | 
			
		||||
        case 'KeyS':
 | 
			
		||||
          this.world.player.vec3.z = 0;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'KeyA':
 | 
			
		||||
        case 'KeyD':
 | 
			
		||||
          this.world.player.vec3.x = 0;
 | 
			
		||||
          break;
 | 
			
		||||
        case 'Space':
 | 
			
		||||
          this.world.player.vec3.y = 0;
 | 
			
		||||
          break;
 | 
			
		||||
      }
 | 
			
		||||
      this.socket.emit('updatePlayer', this.world.player);
 | 
			
		||||
    }, false);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onConnect() {
 | 
			
		||||
    console.log('Connected to Troonia.');
 | 
			
		||||
    this.world.addPlayer();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onDisconnect(message: any) {
 | 
			
		||||
    this.world.removePlayer();
 | 
			
		||||
    console.log(`Disconnected from Troonia: ${message}.`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update() {
 | 
			
		||||
    const delta = this.clock.getDelta();
 | 
			
		||||
    this.world.update(delta);
 | 
			
		||||
    this.renderer.render(this.world.scene, this.camera);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								src/client/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								src/client/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://json.schemastore.org/tsconfig",
 | 
			
		||||
  "display": "Node 16 + ESM + Strictest",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ES2021",
 | 
			
		||||
      "dom"
 | 
			
		||||
    ],
 | 
			
		||||
    "module": "ES2022",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "target": "ES2021",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "allowUnusedLabels": false,
 | 
			
		||||
    "allowUnreachableCode": false,
 | 
			
		||||
    "exactOptionalPropertyTypes": true,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
    "noImplicitOverride": true,
 | 
			
		||||
    "noImplicitReturns": true,
 | 
			
		||||
    "noPropertyAccessFromIndexSignature": true,
 | 
			
		||||
    "noUncheckedIndexedAccess": true,
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "noUnusedParameters": true,
 | 
			
		||||
    "importsNotUsedAsValues": "error",
 | 
			
		||||
    "outDir": "../../dist/client/"
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "../client/*.ts",
 | 
			
		||||
    "../common/*.ts"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/client/util.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/client/util.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
export function get_viewport_size(): {width: number, height: number} {
 | 
			
		||||
  const vw = Math.max(
 | 
			
		||||
      document.documentElement.clientWidth || 0, window.innerWidth || 0);
 | 
			
		||||
  const vh = Math.max(
 | 
			
		||||
      document.documentElement.clientHeight || 0, window.innerHeight || 0);
 | 
			
		||||
  return {width: vw, height: vh};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										51
									
								
								src/client/world.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								src/client/world.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,51 @@
 | 
			
		||||
import type {IBaseWorld} from '../common/world';
 | 
			
		||||
import type {Troonia} from './troonia';
 | 
			
		||||
import {SphereGeometry, Mesh, MeshPhongMaterial, Object3D, Scene} from 'three';
 | 
			
		||||
import {BaseWorld} from '../common/world';
 | 
			
		||||
import {Physics} from './physics';
 | 
			
		||||
import type {TSceneObject} from '../common/scenes';
 | 
			
		||||
import type {Socket} from 'socket.io-client';
 | 
			
		||||
 | 
			
		||||
export class World extends BaseWorld implements IBaseWorld {
 | 
			
		||||
  scene: Scene = new Scene();
 | 
			
		||||
  physics: Physics;
 | 
			
		||||
  player = {vec3: {x: 0, y: 0, z: 0}};
 | 
			
		||||
 | 
			
		||||
  constructor(game: Troonia) {
 | 
			
		||||
    super(game);
 | 
			
		||||
    this.physics = new Physics(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addPlayer(id?: string) {
 | 
			
		||||
    id = id ? id : (<Socket>this.game.socket).id;
 | 
			
		||||
    const player = new Object3D();
 | 
			
		||||
    player.name = id;
 | 
			
		||||
    const mesh = new Mesh(
 | 
			
		||||
        new SphereGeometry(.5), new MeshPhongMaterial({color: 0xFF00FF}));
 | 
			
		||||
    player.add(mesh);
 | 
			
		||||
    this.objects[id] = player;
 | 
			
		||||
    this.scene.add(player);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removePlayer(id?: string) {
 | 
			
		||||
    id = id ? id : (<Socket>this.game.socket).id;
 | 
			
		||||
    this.scene.remove(<Object3D>this.scene.getObjectByName(id));
 | 
			
		||||
    delete this.objects[id];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update(delta: number) {
 | 
			
		||||
    this.physics.update(delta);
 | 
			
		||||
 | 
			
		||||
    const player = <Object3D>this.objects[(<Socket>this.game.socket).id];
 | 
			
		||||
    (<Troonia>this.game).camera.lookAt(player.position);
 | 
			
		||||
    (<Troonia>this.game)
 | 
			
		||||
        .camera.position.set(player.position.x / 2, 50 - Math.abs(player.position.y), player.position.z / 2)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  load(scene: TSceneObject[]) {
 | 
			
		||||
    for (const sceneObject of scene) {
 | 
			
		||||
      this.objects[sceneObject.id] = sceneObject.object;
 | 
			
		||||
      this.scene.add(sceneObject.object);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/common/physics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/common/physics.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
import type {IBaseWorld} from './world.js';
 | 
			
		||||
import type {ICoord, IQuaternion, IVec3} from './threed.js';
 | 
			
		||||
 | 
			
		||||
export interface IPhysicsObject {
 | 
			
		||||
  position: ICoord;
 | 
			
		||||
  quaternion: IQuaternion;
 | 
			
		||||
  angularVelocity?: IVec3;
 | 
			
		||||
  velocity?: IVec3;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export class BasePhysics {
 | 
			
		||||
  baseWorld: IBaseWorld;
 | 
			
		||||
  bodies: {[id: string]: IPhysicsObject} = {};
 | 
			
		||||
  constructor(baseWorld: IBaseWorld) {
 | 
			
		||||
    this.baseWorld = baseWorld;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  setPos(object: IPhysicsObject, body: IPhysicsObject) {
 | 
			
		||||
    object.position.x = body.position.x;
 | 
			
		||||
    object.position.y = body.position.y;
 | 
			
		||||
    object.position.z = body.position.z;
 | 
			
		||||
    object.quaternion.x = body.quaternion.x;
 | 
			
		||||
    object.quaternion.y = body.quaternion.y;
 | 
			
		||||
    object.quaternion.z = body.quaternion.z;
 | 
			
		||||
    object.quaternion.w = body.quaternion.w;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  syncState() {
 | 
			
		||||
    for (const id in this.bodies) {
 | 
			
		||||
      const object = this.baseWorld.objects[id];
 | 
			
		||||
      const body = this.bodies[id];
 | 
			
		||||
 | 
			
		||||
      if (object && body) {
 | 
			
		||||
        this.setPos(object, body);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								src/common/player.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								src/common/player.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
import type {IVec3} from './threed.js';
 | 
			
		||||
 | 
			
		||||
export interface IPlayerData {
 | 
			
		||||
  vec3?: IVec3;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										159
									
								
								src/common/scenes.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										159
									
								
								src/common/scenes.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,159 @@
 | 
			
		||||
import * as CANNON from 'cannon-es';
 | 
			
		||||
import * as THREE from 'three';
 | 
			
		||||
 | 
			
		||||
export type TSceneObject = {
 | 
			
		||||
  id: string; object: THREE.Object3D;
 | 
			
		||||
  shape?: CANNON.Shape;
 | 
			
		||||
  size?: {x: number, y: number, z: number};
 | 
			
		||||
  mass?: number;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const light: TSceneObject = {
 | 
			
		||||
  id: 'light',
 | 
			
		||||
  object: new THREE.AmbientLight(0xFFFFFF, 0.9)
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const floor: TSceneObject = {
 | 
			
		||||
  id: 'floor',
 | 
			
		||||
  object: function() {
 | 
			
		||||
    const floor = new THREE.Object3D();
 | 
			
		||||
    const mesh = new THREE.Mesh(
 | 
			
		||||
        new THREE.BoxGeometry(100, 10, 100, 1),
 | 
			
		||||
        new THREE.MeshPhongMaterial({color: 0x002288}));
 | 
			
		||||
    mesh.receiveShadow = true;
 | 
			
		||||
    floor.add(mesh);
 | 
			
		||||
    const grid = new THREE.GridHelper(100, 20, 0xFF00FF, 0x880088);
 | 
			
		||||
    grid.position.y = 5;
 | 
			
		||||
    floor.add(grid);
 | 
			
		||||
    return floor;
 | 
			
		||||
  }(),
 | 
			
		||||
  // shape: new CANNON.Box(new CANNON.Vec3(50, 0.05, 50)),
 | 
			
		||||
  size: {x: 100, y: 10, z: 100}
 | 
			
		||||
  // shape: new CANNON.Plane()
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const floor2: TSceneObject = {
 | 
			
		||||
  id: 'floor2',
 | 
			
		||||
  object: function() {
 | 
			
		||||
    const floor2 = new THREE.Object3D();
 | 
			
		||||
    const mesh = new THREE.Mesh(
 | 
			
		||||
        new THREE.BoxGeometry(150, 10, 150, 1),
 | 
			
		||||
        new THREE.MeshPhongMaterial({color: 0x7500FF}));
 | 
			
		||||
    mesh.receiveShadow = true;
 | 
			
		||||
    floor2.add(mesh);
 | 
			
		||||
    const grid = new THREE.GridHelper(150, 20, 0xFF00FF, 0x880088);
 | 
			
		||||
    grid.position.y = 5;
 | 
			
		||||
    floor2.add(grid);
 | 
			
		||||
    floor2.position.set(-100, -20, 0);
 | 
			
		||||
    return floor2;
 | 
			
		||||
  }(),
 | 
			
		||||
  // shape: new CANNON.Box(new CANNON.Vec3(50, 0.05, 50)),
 | 
			
		||||
  size: {x: 150, y: 10, z: 150}
 | 
			
		||||
  // shape: new CANNON.Plane()
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const planet: TSceneObject = {
 | 
			
		||||
  id: 'planet',
 | 
			
		||||
  object: function() {
 | 
			
		||||
    const planet = new THREE.Object3D();
 | 
			
		||||
    const mesh = new THREE.Mesh(
 | 
			
		||||
        new THREE.SphereGeometry(75),
 | 
			
		||||
        new THREE.MeshPhongMaterial({color: 0x38761D}));
 | 
			
		||||
    mesh.receiveShadow = true;
 | 
			
		||||
    planet.add(mesh);
 | 
			
		||||
    planet.position.set(200, -20, 0);
 | 
			
		||||
    return planet;
 | 
			
		||||
  }(),
 | 
			
		||||
  shape: new CANNON.Sphere(75),
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const donut: TSceneObject = {
 | 
			
		||||
  id: 'donut',
 | 
			
		||||
  object: function() {
 | 
			
		||||
    const donut = new THREE.Object3D();
 | 
			
		||||
    const mesh = new THREE.Mesh(
 | 
			
		||||
        new THREE.RingGeometry(12.5, 75),
 | 
			
		||||
        new THREE.MeshPhongMaterial({color: 0x877652}));
 | 
			
		||||
    mesh.receiveShadow = true;
 | 
			
		||||
    mesh.rotation.x = -Math.PI / 2;
 | 
			
		||||
    donut.add(mesh);
 | 
			
		||||
    donut.position.set(0, -40, 200);
 | 
			
		||||
    return donut;
 | 
			
		||||
  }(),
 | 
			
		||||
  size: {x: 150, y: 10, z: 150}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const TestScene: TSceneObject[] = [
 | 
			
		||||
  light,
 | 
			
		||||
  floor,
 | 
			
		||||
  floor2,
 | 
			
		||||
  planet,
 | 
			
		||||
  donut,
 | 
			
		||||
  ...function() {
 | 
			
		||||
    let i: number = 1;
 | 
			
		||||
    const boxes: TSceneObject[] = [];
 | 
			
		||||
    do {
 | 
			
		||||
      const box = new THREE.Object3D();
 | 
			
		||||
      const mesh = new THREE.Mesh(
 | 
			
		||||
          new THREE.BoxGeometry(1, 1, 1),
 | 
			
		||||
          new THREE.MeshPhongMaterial({color: Math.sin(i) * 0xFFFFFF}));
 | 
			
		||||
      mesh.receiveShadow = true;
 | 
			
		||||
      box.add(mesh);
 | 
			
		||||
      box.position.x = Math.sin(i) - 0.5;
 | 
			
		||||
      box.position.y = i * 10
 | 
			
		||||
 | 
			
		||||
      boxes.push({id: 'cube_' + i, object: box, mass: Math.random()});
 | 
			
		||||
 | 
			
		||||
      i++;
 | 
			
		||||
    } while (i <= 20)
 | 
			
		||||
    return boxes;
 | 
			
		||||
  }(),
 | 
			
		||||
  ...function() {
 | 
			
		||||
    let i: number = 1;
 | 
			
		||||
    const spheres: TSceneObject[] = [];
 | 
			
		||||
    do {
 | 
			
		||||
      const sphere = new THREE.Object3D();
 | 
			
		||||
      const mesh = new THREE.Mesh(
 | 
			
		||||
          new THREE.SphereGeometry(.5),
 | 
			
		||||
          new THREE.MeshPhongMaterial({color: Math.cos(i) * 0xFFFFFF}));
 | 
			
		||||
      mesh.receiveShadow = true;
 | 
			
		||||
      sphere.add(mesh);
 | 
			
		||||
      sphere.position.x = 5 + Math.sin(i) - 0.5;
 | 
			
		||||
      sphere.position.y = i * 10;
 | 
			
		||||
 | 
			
		||||
      spheres.push({
 | 
			
		||||
        id: 'sphere_' + i,
 | 
			
		||||
        object: sphere,
 | 
			
		||||
        shape: new CANNON.Sphere(.5),
 | 
			
		||||
        mass: Math.random()
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      i++;
 | 
			
		||||
    } while (i <= 20)
 | 
			
		||||
    return spheres;
 | 
			
		||||
  }(),
 | 
			
		||||
  ...function() {
 | 
			
		||||
    let i: number = 1;
 | 
			
		||||
    const objs: TSceneObject[] = [];
 | 
			
		||||
    do {
 | 
			
		||||
      const box = new THREE.Object3D();
 | 
			
		||||
      const mesh = new THREE.Mesh(
 | 
			
		||||
          new THREE.ConeGeometry(.5),
 | 
			
		||||
          new THREE.MeshPhongMaterial({color: Math.tan(i) * 0xFFFFFF}));
 | 
			
		||||
      mesh.receiveShadow = true;
 | 
			
		||||
      box.add(mesh);
 | 
			
		||||
      box.position.x = -5 + Math.sin(i) - 0.5;
 | 
			
		||||
      box.position.y = i * 10;
 | 
			
		||||
 | 
			
		||||
      objs.push({
 | 
			
		||||
        id: 'cone_' + i,
 | 
			
		||||
        object: box,
 | 
			
		||||
        shape: new CANNON.Cylinder(.01, .5, 1),
 | 
			
		||||
        mass: Math.random()
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      i++;
 | 
			
		||||
    } while (i <= 20)
 | 
			
		||||
    return objs;
 | 
			
		||||
  }(),
 | 
			
		||||
];
 | 
			
		||||
							
								
								
									
										11
									
								
								src/common/threed.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								src/common/threed.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,11 @@
 | 
			
		||||
export interface ICoord {
 | 
			
		||||
  x: number;
 | 
			
		||||
  y: number;
 | 
			
		||||
  z: number;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type IVec3 = ICoord;
 | 
			
		||||
 | 
			
		||||
export interface IQuaternion extends ICoord {
 | 
			
		||||
  w: number;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								src/common/troonia.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/common/troonia.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
import type {Server} from 'socket.io';
 | 
			
		||||
import type {Socket} from 'socket.io-client';
 | 
			
		||||
import type {IBaseWorld} from './world.js';
 | 
			
		||||
import {Clock} from 'three';
 | 
			
		||||
import type {IPhysicsObject} from './physics';
 | 
			
		||||
 | 
			
		||||
export type TrooniaState = {
 | 
			
		||||
  objects: {[id: string]: IPhysicsObject};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export abstract class BaseTroonia {
 | 
			
		||||
  clock: Clock = new Clock();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IBaseTroonia extends BaseTroonia {
 | 
			
		||||
  socket: Socket|Server;
 | 
			
		||||
  world: IBaseWorld;
 | 
			
		||||
  update(): void;
 | 
			
		||||
  onConnect(socket?: any): void;
 | 
			
		||||
  onDisconnect(message: any, socket?: any): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										23
									
								
								src/common/world.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								src/common/world.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
import type {IPhysicsObject} from './physics.js';
 | 
			
		||||
import type {IBaseTroonia} from './troonia.js';
 | 
			
		||||
import type {BasePhysics} from './physics.js';
 | 
			
		||||
import type {TSceneObject} from './scenes.js'
 | 
			
		||||
import type {IPlayerData} from './player.js';
 | 
			
		||||
 | 
			
		||||
export abstract class BaseWorld {
 | 
			
		||||
  game: IBaseTroonia;
 | 
			
		||||
  objects: {[id: string]: IPhysicsObject} = {};
 | 
			
		||||
  players: {[id: string]: IPlayerData} = {};
 | 
			
		||||
 | 
			
		||||
  constructor(game: IBaseTroonia) {
 | 
			
		||||
    this.game = game;
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export interface IBaseWorld extends BaseWorld {
 | 
			
		||||
  physics: BasePhysics;
 | 
			
		||||
  addPlayer(id?: string): void;
 | 
			
		||||
  removePlayer(id?: string): void;
 | 
			
		||||
  update(delta: number): void;
 | 
			
		||||
  load(scene: TSceneObject[]): void;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								src/server/main.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								src/server/main.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
import express from 'express';
 | 
			
		||||
import http from 'http';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
import {Server} from 'socket.io';
 | 
			
		||||
import {fileURLToPath} from 'url';
 | 
			
		||||
 | 
			
		||||
import {Troonia} from './troonia.js';
 | 
			
		||||
 | 
			
		||||
const port: number = 3001;
 | 
			
		||||
 | 
			
		||||
class App {
 | 
			
		||||
  private readonly server: http.Server;
 | 
			
		||||
  private readonly port: number;
 | 
			
		||||
 | 
			
		||||
  private readonly io: Server;
 | 
			
		||||
 | 
			
		||||
  constructor(port: number) {
 | 
			
		||||
    this.port = port;
 | 
			
		||||
    const app = express();
 | 
			
		||||
 | 
			
		||||
    const __filename = fileURLToPath(import.meta.url);
 | 
			
		||||
    const __dirname = path.dirname(__filename);
 | 
			
		||||
 | 
			
		||||
    app.use(express.static(path.join(__dirname, '../../client')));
 | 
			
		||||
 | 
			
		||||
    this.server = new http.Server(app);
 | 
			
		||||
 | 
			
		||||
    this.io = new Server(this.server);
 | 
			
		||||
    new Troonia(this.io);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public Start() {
 | 
			
		||||
    this.server.listen(this.port, () => {
 | 
			
		||||
      console.log(`Server listening on port ${this.port}.`);
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
new App(port).Start();
 | 
			
		||||
							
								
								
									
										71
									
								
								src/server/physics.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								src/server/physics.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,71 @@
 | 
			
		||||
import {Body, Box, Shape, Vec3, World as PhysicsWorld} from 'cannon-es';
 | 
			
		||||
import type {World as GameWorld} from './world.js';
 | 
			
		||||
import type {IPhysicsObject} from '../common/physics.js';
 | 
			
		||||
import {BasePhysics} from '../common/physics.js';
 | 
			
		||||
import type {IVec3} from '../common/threed';
 | 
			
		||||
 | 
			
		||||
export class Physics extends BasePhysics {
 | 
			
		||||
  world: PhysicsWorld = new PhysicsWorld();
 | 
			
		||||
 | 
			
		||||
  constructor(baseWorld: GameWorld) {
 | 
			
		||||
    super(baseWorld);
 | 
			
		||||
    this.world.gravity.set(0, -9.82, 0);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addObject(
 | 
			
		||||
      id: string, object: IPhysicsObject, mass: number, size: IVec3,
 | 
			
		||||
      shape?: Shape) {
 | 
			
		||||
    const body = new Body({mass: mass});
 | 
			
		||||
 | 
			
		||||
    if (!shape) {
 | 
			
		||||
      body.addShape(new Box(new Vec3(size.x / 2, size.y / 2, size.z / 2)));
 | 
			
		||||
    } else {
 | 
			
		||||
      body.addShape(shape);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    body.position.set(object.position.x, object.position.y, object.position.z);
 | 
			
		||||
    body.quaternion.set(
 | 
			
		||||
        object.quaternion.x, object.quaternion.y, object.quaternion.z,
 | 
			
		||||
        object.quaternion.w);
 | 
			
		||||
 | 
			
		||||
    this.bodies[id] = body;
 | 
			
		||||
    this.world.addBody(body);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removeObject(id: string) {
 | 
			
		||||
    this.world.removeBody(<Body>this.bodies[id]);
 | 
			
		||||
    delete this.bodies[id];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update(delta: number) {
 | 
			
		||||
    for (const player in (<GameWorld>this.baseWorld).players) {
 | 
			
		||||
      // (this.bodies[player] as Body)
 | 
			
		||||
      //     .angularVelocity.copy(
 | 
			
		||||
      //         (this.baseWorld as GameWorld).players[player]?.vec3 as Vec3);
 | 
			
		||||
 | 
			
		||||
      let px = this.baseWorld?.players[player]?.vec3?.x;
 | 
			
		||||
      let py = this.baseWorld?.players[player]?.vec3?.y;
 | 
			
		||||
      let pz = this.baseWorld?.players[player]?.vec3?.z;
 | 
			
		||||
      px = px ? px : 0;
 | 
			
		||||
      py = py ? py : 0;
 | 
			
		||||
      pz = pz ? pz : 0;
 | 
			
		||||
      if (px > 0) {
 | 
			
		||||
        (<Body>this.bodies[player]).angularVelocity.x += 1;
 | 
			
		||||
      }
 | 
			
		||||
      if (py > 0) {
 | 
			
		||||
        (<Body>this.bodies[player]).velocity.y += 0.5;
 | 
			
		||||
      }
 | 
			
		||||
      if (pz > 0) {
 | 
			
		||||
        (<Body>this.bodies[player]).angularVelocity.z += 1;
 | 
			
		||||
      }
 | 
			
		||||
      if (px < 0) {
 | 
			
		||||
        (<Body>this.bodies[player]).angularVelocity.x -= 1;
 | 
			
		||||
      }
 | 
			
		||||
      if (pz < 0) {
 | 
			
		||||
        (<Body>this.bodies[player]).angularVelocity.z -= 1;
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    this.world.fixedStep(delta);
 | 
			
		||||
    this.syncState();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										70
									
								
								src/server/troonia.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										70
									
								
								src/server/troonia.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,70 @@
 | 
			
		||||
import type {IBaseTroonia} from '../common/troonia.js';
 | 
			
		||||
import type {Server} from 'socket.io';
 | 
			
		||||
import {World} from './world.js';
 | 
			
		||||
import {BaseTroonia} from '../common/troonia.js';
 | 
			
		||||
import type {Socket} from 'socket.io';
 | 
			
		||||
import {TestScene} from '../common/scenes.js';
 | 
			
		||||
// import type {Body} from 'cannon-es';
 | 
			
		||||
import type {IPlayerData} from '../common/player.js';
 | 
			
		||||
 | 
			
		||||
export class Troonia extends BaseTroonia implements IBaseTroonia {
 | 
			
		||||
  socket: Server;
 | 
			
		||||
  world: World;
 | 
			
		||||
 | 
			
		||||
  constructor(socket: Server) {
 | 
			
		||||
    super();
 | 
			
		||||
    this.socket = socket;
 | 
			
		||||
 | 
			
		||||
    this.socket.on('connection', (socket: any) => {
 | 
			
		||||
      this.onConnect(socket);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    this.world = new World(this);
 | 
			
		||||
    this.world.load(TestScene);
 | 
			
		||||
 | 
			
		||||
    setInterval(() => {
 | 
			
		||||
      this.socket.emit('syncState', {objects: this.world.objects});
 | 
			
		||||
    }, 1000 / 20);
 | 
			
		||||
 | 
			
		||||
    setInterval(() => {
 | 
			
		||||
      this.update();
 | 
			
		||||
    }, 1000 / 50);
 | 
			
		||||
 | 
			
		||||
    // setInterval(() => {
 | 
			
		||||
    //   this.world.objects = {};
 | 
			
		||||
    //   for (const body of Object.values(this.world.physics.bodies)) {
 | 
			
		||||
    //     this.world.physics.world.removeBody(body as Body);
 | 
			
		||||
    //   }
 | 
			
		||||
    //   this.world.physics.bodies = {};
 | 
			
		||||
    //   this.world.load(TestScene);
 | 
			
		||||
    // }, 1000 * 10);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onConnect(socket: Socket) {
 | 
			
		||||
    console.log(`User ${socket.id} connected.`);
 | 
			
		||||
 | 
			
		||||
    socket.emit('syncPlayers', this.world.players);
 | 
			
		||||
 | 
			
		||||
    this.socket.emit('addPlayer', socket.id);
 | 
			
		||||
    this.world.addPlayer(socket.id);
 | 
			
		||||
 | 
			
		||||
    socket.on('disconnect', (message: any) => {
 | 
			
		||||
      this.onDisconnect(message, socket);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    socket.on('updatePlayer', (data: IPlayerData) => {
 | 
			
		||||
      this.world.players[socket.id] = data;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  onDisconnect(message: any, socket: any) {
 | 
			
		||||
    this.world.removePlayer(socket.id);
 | 
			
		||||
    this.socket.emit('removePlayer', socket.id);
 | 
			
		||||
    console.log(`User ${socket.id} disconnected: ${message}.`);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update() {
 | 
			
		||||
    const delta = this.clock.getDelta();
 | 
			
		||||
    this.world.update(delta);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										32
									
								
								src/server/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								src/server/tsconfig.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,32 @@
 | 
			
		||||
{
 | 
			
		||||
  "$schema": "https://json.schemastore.org/tsconfig",
 | 
			
		||||
  "display": "Node 16 + ESM + Strictest",
 | 
			
		||||
  "compilerOptions": {
 | 
			
		||||
    "lib": [
 | 
			
		||||
      "ES2021"
 | 
			
		||||
    ],
 | 
			
		||||
    "module": "ES2022",
 | 
			
		||||
    "moduleResolution": "Node",
 | 
			
		||||
    "target": "ES2021",
 | 
			
		||||
    "strict": true,
 | 
			
		||||
    "esModuleInterop": true,
 | 
			
		||||
    "skipLibCheck": true,
 | 
			
		||||
    "forceConsistentCasingInFileNames": true,
 | 
			
		||||
    "allowUnusedLabels": false,
 | 
			
		||||
    "allowUnreachableCode": false,
 | 
			
		||||
    "exactOptionalPropertyTypes": true,
 | 
			
		||||
    "noFallthroughCasesInSwitch": true,
 | 
			
		||||
    "noImplicitOverride": true,
 | 
			
		||||
    "noImplicitReturns": true,
 | 
			
		||||
    "noPropertyAccessFromIndexSignature": true,
 | 
			
		||||
    "noUncheckedIndexedAccess": true,
 | 
			
		||||
    "noUnusedLocals": true,
 | 
			
		||||
    "noUnusedParameters": true,
 | 
			
		||||
    "importsNotUsedAsValues": "error",
 | 
			
		||||
    "outDir": "../../dist/server"
 | 
			
		||||
  },
 | 
			
		||||
  "include": [
 | 
			
		||||
    "../server/*.ts",
 | 
			
		||||
    "../common/*.ts"
 | 
			
		||||
  ]
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										81
									
								
								src/server/world.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								src/server/world.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
			
		||||
import type {IBaseWorld} from '../common/world.js';
 | 
			
		||||
import type {IPhysicsObject} from '../common/physics.js'
 | 
			
		||||
import {Physics} from './physics.js';
 | 
			
		||||
import {Box3} from 'three';
 | 
			
		||||
import {BaseWorld} from '../common/world.js';
 | 
			
		||||
import type {Troonia} from './troonia.js';
 | 
			
		||||
import type {TSceneObject} from '../common/scenes.js';
 | 
			
		||||
import {Sphere} from 'cannon-es';
 | 
			
		||||
import type {IVec3} from '../common/threed';
 | 
			
		||||
 | 
			
		||||
export class World extends BaseWorld implements IBaseWorld {
 | 
			
		||||
  physics: Physics;
 | 
			
		||||
 | 
			
		||||
  constructor(game: Troonia) {
 | 
			
		||||
    super(game);
 | 
			
		||||
    this.physics = new Physics(this);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  addPlayer(id: string) {
 | 
			
		||||
    const playerObject: IPhysicsObject = {
 | 
			
		||||
      position: {x: Math.random() * 90 - 45, y: 10, z: Math.random() * 90 - 45},
 | 
			
		||||
      quaternion: {x: 0, y: 0, z: 0, w: 1}
 | 
			
		||||
    };
 | 
			
		||||
    this.objects[id] = playerObject;
 | 
			
		||||
    this.physics.addObject(
 | 
			
		||||
        id, playerObject, 10, {x: 1, y: 1, z: 1}, new Sphere(.5));
 | 
			
		||||
    this.players[id] = {vec3: {x: 0, y: 0, z: 0}};
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  removePlayer(id: string) {
 | 
			
		||||
    delete this.players[id];
 | 
			
		||||
    delete this.objects[id];
 | 
			
		||||
    this.physics.removeObject(id);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  update(delta: number) {
 | 
			
		||||
    this.physics.update(delta);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  load(scene: TSceneObject[]) {
 | 
			
		||||
    for (const sceneObject of scene) {
 | 
			
		||||
      const physicsObject: IPhysicsObject = {
 | 
			
		||||
        position: {x: 0, y: 0, z: 0},
 | 
			
		||||
        quaternion: {x: 0, y: 0, z: 0, w: 0}
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      physicsObject.position = {
 | 
			
		||||
        x: sceneObject.object.position.x,
 | 
			
		||||
        y: sceneObject.object.position.y,
 | 
			
		||||
        z: sceneObject.object.position.z
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      physicsObject.quaternion = {
 | 
			
		||||
        x: sceneObject.object.quaternion.x,
 | 
			
		||||
        y: sceneObject.object.quaternion.y,
 | 
			
		||||
        z: sceneObject.object.quaternion.z,
 | 
			
		||||
        w: sceneObject.object.quaternion.w
 | 
			
		||||
      };
 | 
			
		||||
 | 
			
		||||
      let size: IVec3;
 | 
			
		||||
      if (!sceneObject.size) {
 | 
			
		||||
        size = function() {
 | 
			
		||||
          const bounds = new Box3().setFromObject(sceneObject.object);
 | 
			
		||||
          return {
 | 
			
		||||
            x: Math.abs(bounds.max.x - bounds.min.x),
 | 
			
		||||
            y: Math.abs(bounds.max.y - bounds.min.y),
 | 
			
		||||
            z: Math.abs(bounds.max.y - bounds.min.y)
 | 
			
		||||
          };
 | 
			
		||||
        }();
 | 
			
		||||
      } else {
 | 
			
		||||
        size = sceneObject.size;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      let mass = ((sceneObject.mass) ? sceneObject.mass : 0);
 | 
			
		||||
 | 
			
		||||
      this.objects[sceneObject.id] = physicsObject;
 | 
			
		||||
      this.physics.addObject(
 | 
			
		||||
          sceneObject.id, physicsObject, mass, size, sceneObject.shape);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										31
									
								
								webpack.common.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								webpack.common.cjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,31 @@
 | 
			
		||||
const path = require("path");
 | 
			
		||||
const CopyPlugin = require("copy-webpack-plugin");
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  entry: './src/client/main.ts',
 | 
			
		||||
  plugins: [
 | 
			
		||||
      new CopyPlugin({
 | 
			
		||||
        patterns: [{
 | 
			
		||||
          from: "index.html",
 | 
			
		||||
          to: "."
 | 
			
		||||
        }]
 | 
			
		||||
      })
 | 
			
		||||
  ],
 | 
			
		||||
  module: {
 | 
			
		||||
    rules: [
 | 
			
		||||
      {
 | 
			
		||||
        test: /\.tsx?$/,
 | 
			
		||||
        use: 'ts-loader',
 | 
			
		||||
        exclude: [/node_modules/, /server/],
 | 
			
		||||
      },
 | 
			
		||||
    ],
 | 
			
		||||
  },
 | 
			
		||||
  resolve: {
 | 
			
		||||
    alias: {three: path.resolve('./node_modules/three/')},
 | 
			
		||||
    extensions: ['.tsx', '.ts', '.js'],
 | 
			
		||||
  },
 | 
			
		||||
  output: {
 | 
			
		||||
    filename: 'bundle.js',
 | 
			
		||||
    path: path.resolve(__dirname, './dist/client/'),
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										16
									
								
								webpack.dev.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								webpack.dev.cjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
const { merge } = require("webpack-merge");
 | 
			
		||||
const common = require("./webpack.common.cjs");
 | 
			
		||||
const path = require("path");
 | 
			
		||||
 | 
			
		||||
// noinspection JSCheckFunctionSignatures
 | 
			
		||||
module.exports = merge(common, {
 | 
			
		||||
  mode: 'development',
 | 
			
		||||
  devtool: 'eval-source-map',
 | 
			
		||||
  devServer: {
 | 
			
		||||
    static: {
 | 
			
		||||
      directory: path.join(__dirname, 'dist/client'),
 | 
			
		||||
    },
 | 
			
		||||
    hot: true,
 | 
			
		||||
    proxy: {'/socket.io': {target: 'http://127.0.0.1:3001', ws: true}}
 | 
			
		||||
  }
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										6
									
								
								webpack.prod.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								webpack.prod.cjs
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,6 @@
 | 
			
		||||
const { merge } = require("webpack-merge");
 | 
			
		||||
const common = require("./webpack.common.cjs");
 | 
			
		||||
 | 
			
		||||
// noinspection JSCheckFunctionSignatures
 | 
			
		||||
module.exports =
 | 
			
		||||
    merge(common, {mode: 'production', performance: {hints: false}});
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user