diff --git a/docs/package-lock.json b/docs/package-lock.json index 9fd393f..1e482bf 100644 --- a/docs/package-lock.json +++ b/docs/package-lock.json @@ -2438,12 +2438,14 @@ "balanced-match": { "version": "1.0.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "bundled": true, "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -2458,17 +2460,20 @@ "code-point-at": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -2585,7 +2590,8 @@ "inherits": { "version": "2.0.3", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -2597,6 +2603,7 @@ "version": "1.0.0", "bundled": true, "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -2611,6 +2618,7 @@ "version": "3.0.4", "bundled": true, "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -2722,7 +2730,8 @@ "number-is-nan": { "version": "1.0.1", "bundled": true, - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -2855,6 +2864,7 @@ "version": "1.0.2", "bundled": true, "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", diff --git a/docs/webpack.config.js b/docs/webpack.config.js index c5bbe2a..f70bcbd 100644 --- a/docs/webpack.config.js +++ b/docs/webpack.config.js @@ -28,7 +28,7 @@ module.exports = { loader: "awesome-typescript-loader", }, { - test: /\.(glb|mtl|png|jpe?g|gif)$/, + test: /\.(glb|mtl|png|jpe?g|gif|hdr|exr)$/, use: [ { loader: "file-loader", diff --git a/package-lock.json b/package-lock.json index 7a117d2..69dd65d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2957,14 +2957,12 @@ "minimist": { "version": "0.0.8", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "minipass": { "version": "2.3.5", "bundled": true, "dev": true, - "optional": true, "requires": { "safe-buffer": "^5.1.2", "yallist": "^3.0.0" @@ -2983,7 +2981,6 @@ "version": "0.5.1", "bundled": true, "dev": true, - "optional": true, "requires": { "minimist": "0.0.8" } @@ -3163,8 +3160,7 @@ "safe-buffer": { "version": "5.1.2", "bundled": true, - "dev": true, - "optional": true + "dev": true }, "safer-buffer": { "version": "2.1.2", @@ -3270,8 +3266,7 @@ "yallist": { "version": "3.0.3", "bundled": true, - "dev": true, - "optional": true + "dev": true } } }, diff --git a/src/builders/GameBuilder.ts b/src/builders/GameBuilder.ts index 8085aaa..90d6e09 100644 --- a/src/builders/GameBuilder.ts +++ b/src/builders/GameBuilder.ts @@ -1,4 +1,4 @@ -import { LoadingManager } from "three" +import { LoadingManager, WebGLRenderer } from "three" import CameraManager from "../managers/CameraManager" import DataManager from "../managers/DataManager" @@ -26,7 +26,8 @@ const defaultGameBuilder = async ({ defaultLoadouts }: GameBuilderOptions) => { const players = replayMetadata.players - const sceneManager = await defaultSceneBuilder(players, loadingManager, defaultLoadouts) + const renderer = new WebGLRenderer({ antialias: true }) + const sceneManager = await defaultSceneBuilder(players, renderer, loadingManager, defaultLoadouts) defaultAnimationBuilder(replayData, sceneManager.players, sceneManager.ball) DataManager.init({ replayData, replayMetadata }) CameraManager.init() @@ -34,6 +35,7 @@ const defaultGameBuilder = async ({ return GameManager.init({ clock, + renderer }) } diff --git a/src/builders/SceneBuilder.ts b/src/builders/SceneBuilder.ts index aaec05c..8af1854 100644 --- a/src/builders/SceneBuilder.ts +++ b/src/builders/SceneBuilder.ts @@ -1,5 +1,5 @@ import { RocketAssetManager, RocketConfig, TextureFormat } from "rl-loadout-lib" -import { Cache, LoadingManager, Scene } from "three" +import { Cache, LoadingManager, Scene, WebGLRenderer } from "three" import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader" import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader" @@ -11,6 +11,7 @@ import { buildBall } from "./ball/buildBall" import { buildPlayfield } from "./field/buildPlayfield" import { buildRocketLoadoutGroup } from "./player/buildRocketLoadoutScene" import { addLighting } from "./scene/addLighting" +import { addEnvironment } from "./scene/addEnvironment"; /** * @description The sole purpose of this function is to initialize and tie together all of the @@ -20,6 +21,7 @@ import { addLighting } from "./scene/addLighting" */ const defaultSceneBuilder = async ( playerInfo: ExtendedPlayer[], + renderer: WebGLRenderer, loadingManager?: LoadingManager, defaultLoadouts?: boolean ): Promise => { @@ -44,17 +46,18 @@ const defaultSceneBuilder = async ( useCompressedModels: true, }) const manager = new RocketAssetManager(config) - const bodyPromises = playerInfo.map(player => - loadRlLoadout(manager, player, defaultLoadouts) - ) await GameFieldAssets.load() - const bodies = await Promise.all(bodyPromises) addLighting(scene) - const field = buildPlayfield(scene) + const envMap = addEnvironment(scene, renderer) + const bodyPromises = playerInfo.map(player => + loadRlLoadout(manager, player, envMap, defaultLoadouts) + ) + const bodies = await Promise.all(bodyPromises) + const field = buildPlayfield(scene, envMap) const players = bodies.map(value => buildRocketLoadoutGroup(scene, value)) - const ball = buildBall(scene) + const ball = buildBall(scene, envMap) return SceneManager.init({ scene, diff --git a/src/builders/ball/buildBall.ts b/src/builders/ball/buildBall.ts index 5c0a2db..0650471 100644 --- a/src/builders/ball/buildBall.ts +++ b/src/builders/ball/buildBall.ts @@ -1,14 +1,19 @@ -import { Scene } from "three" +import { Scene, Mesh, MeshStandardMaterial, Texture } from "three" import { BALL } from "../../constants/gameObjectNames" import GameFieldAssets from "../../loaders/scenes/GameFieldAssets" import BallManager from "../../managers/models/BallManager" -export const buildBall = (scene: Scene) => { +export const buildBall = (scene: Scene, envMap: Texture) => { const { ball } = GameFieldAssets.getAssets() ball.scale.setScalar(105) ball.name = BALL ball.castShadow = true + ball.traverse( child => { + if ( (child as Mesh).isMesh ) { + ((child as Mesh).material as MeshStandardMaterial).envMap = envMap + } + }) scene.add(ball) return new BallManager(ball) } diff --git a/src/builders/field/buildPlayfield.ts b/src/builders/field/buildPlayfield.ts index acfa664..17cf251 100644 --- a/src/builders/field/buildPlayfield.ts +++ b/src/builders/field/buildPlayfield.ts @@ -4,34 +4,42 @@ import { MeshPhongMaterial, PlaneBufferGeometry, Scene, + Texture, + MeshStandardMaterial, } from "three" import GameFieldAssets from "../../loaders/scenes/GameFieldAssets" import FieldManager from "../../managers/models/FieldManager" import { addCameras } from "./addCameras" -export const buildPlayfield = (scene: Scene) => { +export const buildPlayfield = (scene: Scene, envMap: Texture) => { /** * Temporary */ - const goalPlane = new PlaneBufferGeometry(2000, 1284.5, 1, 1) + const goalPlane = new PlaneBufferGeometry(1800, 630, 1, 1) const blueGoalMaterial = new MeshPhongMaterial({ color: "#2196f3", side: DoubleSide, opacity: 0.3, transparent: true, + emissive: "#2196f3", + emissiveIntensity: 0.7 }) const orangeGoalMaterial = new MeshPhongMaterial({ color: "#ff9800", side: DoubleSide, opacity: 0.3, transparent: true, + emissive: "#ff9800", + emissiveIntensity: 0.7 }) const blueGoal = new Mesh(goalPlane, blueGoalMaterial) blueGoal.position.z = -5120 + blueGoal.position.y = 315 scene.add(blueGoal) const orangeGoal = new Mesh(goalPlane, orangeGoalMaterial) orangeGoal.position.z = 5120 + orangeGoal.position.y = 315 orangeGoal.rotation.y = Math.PI scene.add(orangeGoal) /** @@ -40,8 +48,12 @@ export const buildPlayfield = (scene: Scene) => { const { field } = GameFieldAssets.getAssets() field.scale.setScalar(400) - - field.children.forEach(child => (child.receiveShadow = true)) + field.traverse(child => { + child.receiveShadow = true; + if ((child as Mesh).isMesh) { + ((child as Mesh).material as MeshStandardMaterial).envMap = envMap + } + }) field.receiveShadow = true scene.add(field) diff --git a/src/builders/scene/addEnvironment.ts b/src/builders/scene/addEnvironment.ts new file mode 100644 index 0000000..6b308f6 --- /dev/null +++ b/src/builders/scene/addEnvironment.ts @@ -0,0 +1,26 @@ +import { Scene, WebGLRenderer, WebGLRenderTargetCube } from "three" + +import { PMREMGenerator } from "three/examples/jsm/pmrem/PMREMGenerator" +import { PMREMCubeUVPacker } from "three/examples/jsm/pmrem/PMREMCubeUVPacker" +import GameFieldAssets from "../../loaders/scenes/GameFieldAssets" + +export const addEnvironment = (scene: Scene, renderer: WebGLRenderer) => { + const { environment } = GameFieldAssets.getAssets() + + const cubeMap = new WebGLRenderTargetCube(2048, 2048).fromEquirectangularTexture(renderer, environment) + + const pmremGenerator = new PMREMGenerator( cubeMap.texture ); + pmremGenerator.update( renderer ); + + const pmremCubeUVPacker = new PMREMCubeUVPacker( pmremGenerator.cubeLods ); + pmremCubeUVPacker.update( renderer ); + + // @ts-ignore + scene.background = cubeMap; + + pmremGenerator.dispose(); + pmremCubeUVPacker.dispose(); + + const envMap = pmremCubeUVPacker.CubeUVRenderTarget.texture; + return envMap +} diff --git a/src/builders/scene/addLighting.ts b/src/builders/scene/addLighting.ts index 355dce8..85a35fd 100644 --- a/src/builders/scene/addLighting.ts +++ b/src/builders/scene/addLighting.ts @@ -1,14 +1,6 @@ -import { AmbientLight, DirectionalLight, HemisphereLight, Scene } from "three" +import { DirectionalLight, Scene } from "three" export const addLighting = (scene: Scene) => { - // Ambient light provides uniform lighting to all objects in the scene - const ambientLight = new AmbientLight(0xffffff, 0.2) - scene.add(ambientLight) - - // Hemisphere light gives the cars their shine and color - const hemisphereLight = new HemisphereLight(0xffffbb, 0xffffff, 0.5) - scene.add(hemisphereLight) - // The directional light is purely responsible for casting shadows const dirLight = new DirectionalLight(0xffffff, 1.5) dirLight.color.setHSL(0.1, 1, 0.95) diff --git a/src/loaders/operators/loadRGBE.ts b/src/loaders/operators/loadRGBE.ts new file mode 100644 index 0000000..dc5e4a4 --- /dev/null +++ b/src/loaders/operators/loadRGBE.ts @@ -0,0 +1,24 @@ +import { LoadingManager, DataTexture, UnsignedByteType } from "three" +import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader" + +export const loadRGBE = (path: string, loadingManager?: LoadingManager) => { + return new Promise( + ( + resolve: (rgbeTexture: DataTexture) => void, + reject: (err: Error | ErrorEvent) => void + ) => { + const rgbeLoader = new RGBELoader(loadingManager) + rgbeLoader.setDataType( UnsignedByteType ) + rgbeLoader.load( + path, + (rgbeTexture: DataTexture) => { + resolve(rgbeTexture) + }, + undefined, + (error: Error | ErrorEvent) => { + reject(error) + } + ) + } + ) +} diff --git a/src/loaders/scenes/GameFieldAssets.ts b/src/loaders/scenes/GameFieldAssets.ts index c4969fa..3d073e1 100644 --- a/src/loaders/scenes/GameFieldAssets.ts +++ b/src/loaders/scenes/GameFieldAssets.ts @@ -1,11 +1,13 @@ -import { Group, LoadingManager, Object3D } from "three" +import { Group, LoadingManager, Object3D, DataTexture } from "three" import { loadBall } from "../storage/loadBall" import { loadField } from "../storage/loadField" +import { loadEnvironment } from "../storage/loadEnvironment" interface AvailableAssets { ball: Object3D field: Group + environment: DataTexture } class GameFieldAssets { @@ -20,11 +22,13 @@ class GameFieldAssets { const lm = this.loadingManager return Promise.all([ loadBall(lm), - loadField(lm) - ]).then(([ball, field]) => { + loadField(lm), + loadEnvironment(lm) + ]).then(([ball, field, environment]) => { this.assets = { ball, - field + field, + environment } as AvailableAssets }) } diff --git a/src/loaders/storage/loadEnvironment.ts b/src/loaders/storage/loadEnvironment.ts new file mode 100644 index 0000000..2807892 --- /dev/null +++ b/src/loaders/storage/loadEnvironment.ts @@ -0,0 +1,15 @@ +import { LoadingManager, DataTexture } from "three" + +import { loadRGBE } from "../operators/loadRGBE" +// import { loadEXR } from "../operators/loadEXR" +import { storageMemoize } from "./storageMemoize" + +export const loadEnvironment = (loadingManager?: LoadingManager) => + storageMemoize(async () => { + const { default: hdr } = await import( + // @ts-ignore + /* webpackChunkName: "Environment" */ "../../assets/models/textures/Environment/Environment.hdr" + ) + const environmentTexture = await loadRGBE(hdr, loadingManager) + return environmentTexture as DataTexture + }, "ENVIRONMENT") diff --git a/src/loaders/storage/loadRlLoadout.ts b/src/loaders/storage/loadRlLoadout.ts index 90210af..fb4b63a 100644 --- a/src/loaders/storage/loadRlLoadout.ts +++ b/src/loaders/storage/loadRlLoadout.ts @@ -7,13 +7,14 @@ import { Wheel, WheelsModel, } from "rl-loadout-lib" -import { Mesh, MeshPhongMaterial, MeshStandardMaterial } from "three" +import { Mesh, MeshStandardMaterial, Texture } from "three" import { ExtendedPlayer } from "../../models/ReplayMetadata" export const loadRlLoadout = async ( manager: RocketAssetManager, player: ExtendedPlayer, + envMap: Texture, defaultLoadout?: boolean ): Promise<{ body: BodyModel; player: ExtendedPlayer }> => { let body: BodyModel @@ -54,26 +55,10 @@ export const loadRlLoadout = async ( body.addWheelsModel(wheels) body.scene.traverse(object => { - // @ts-ignore - if (object.isMesh) { - const mesh = object as Mesh - mesh.receiveShadow = true - mesh.castShadow = true - - // Phong material is less physically accurate but has noticably better performance - const oldMaterial = mesh.material as MeshStandardMaterial - const phongMaterial = new MeshPhongMaterial() - phongMaterial.name = oldMaterial.name - phongMaterial.map = oldMaterial.map - phongMaterial.normalMap = oldMaterial.normalMap - phongMaterial.color = oldMaterial.color - phongMaterial.shininess = (1 - oldMaterial.roughness) * 100 - phongMaterial.skinning = oldMaterial.skinning - - mesh.material = phongMaterial - mesh.material.needsUpdate = true - - oldMaterial.dispose() + if ((object as Mesh).isMesh) { + object.receiveShadow = true + object.castShadow = true; + ((object as Mesh).material as MeshStandardMaterial).envMap = envMap } }) diff --git a/src/managers/GameManager.ts b/src/managers/GameManager.ts index 1d5c5d6..8278445 100644 --- a/src/managers/GameManager.ts +++ b/src/managers/GameManager.ts @@ -28,15 +28,16 @@ import KeyManager from "./KeyManager" import SceneManager from "./SceneManager" interface GameManagerOptions { - clock: FPSClock + clock: FPSClock, + renderer: WebGLRenderer } export class GameManager { clock: FPSClock - private readonly renderer: WebGLRenderer + renderer: WebGLRenderer - private constructor({ clock }: GameManagerOptions) { - this.renderer = new WebGLRenderer({ antialias: true }) + private constructor({ clock, renderer }: GameManagerOptions) { + this.renderer = renderer this.renderer.shadowMap.enabled = true this.animate = this.animate.bind(this) this.render = this.render.bind(this) diff --git a/src/types/exr.d.ts b/src/types/exr.d.ts new file mode 100644 index 0000000..f633b30 --- /dev/null +++ b/src/types/exr.d.ts @@ -0,0 +1,4 @@ +declare module "*.exr" { + const value: string + export default value +} diff --git a/src/types/hdr.d.ts b/src/types/hdr.d.ts new file mode 100644 index 0000000..664d98d --- /dev/null +++ b/src/types/hdr.d.ts @@ -0,0 +1,4 @@ +declare module "*.hdr" { + const value: string + export default value +} diff --git a/webpack.config.js b/webpack.config.js index 052d842..8b0e4a9 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -35,7 +35,7 @@ module.exports = { }, }, { - test: /\.(glb|mtl)$/, + test: /\.(glb|mtl|hdr|exr)$/, use: [ { loader: "file-loader",