initial commit

This commit is contained in:
vance 2022-10-23 22:20:36 -07:00
commit b2a0e586b2
24 changed files with 918 additions and 0 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
/dist
/node_modules
/.idea
package-lock.json

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# troonia
the conjugation of the spheres

11
index.html Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View File

@ -0,0 +1,5 @@
import type {IVec3} from './threed.js';
export interface IPlayerData {
vec3?: IVec3;
}

159
src/common/scenes.ts Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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}});