diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index fad70a65a..9e692d113 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,6 @@ # These are supported funding model platforms -github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] +github: [BarthPaleologue] # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2] patreon: # Replace with a single Patreon username open_collective: # Replace with a single Open Collective username ko_fi: cosmosjourneyer diff --git a/package.json b/package.json index 5ac9d1ac9..d954635d0 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, - "version": "1.4.0", + "version": "1.4.1", "description": "CosmosJourneyer", "name": "cosmos-journeyer", "scripts": { diff --git a/src/html/pauseMenu.html b/src/html/pauseMenu.html index a5ce2f97d..bafc2dc2c 100644 --- a/src/html/pauseMenu.html +++ b/src/html/pauseMenu.html @@ -1,11 +1,12 @@ -
+
+
-
  • Share Position
  • - \ No newline at end of file diff --git a/src/shaders/blackhole.glsl b/src/shaders/blackhole.glsl index 793142e19..79cdcf2be 100644 --- a/src/shaders/blackhole.glsl +++ b/src/shaders/blackhole.glsl @@ -170,13 +170,13 @@ void main() { vec4 colOut = vec4(0.0); - vec3 positionBHS = camera_position - object_position;// position of the camera in blackhole space + vec3 rayPositionBlackHoleSpace = camera_position - object_position;// position of the camera in blackhole space bool suckedInBH = false; bool escapedBH = false; bool occluded = false; - if (maximumDistance < length(positionBHS)) occluded = true; + if (maximumDistance < length(rayPositionBlackHoleSpace)) occluded = true; vec4 col = vec4(0.0); vec4 glow = vec4(0.0); @@ -193,12 +193,12 @@ void main() { for (int h = 0; h < 6; h++) { //reduces tests for exit conditions (to minimise branching) - distanceToCenter = customLength(positionBHS);//distance to BH - vec3 blackholeDir = -positionBHS / distanceToCenter;//direction to BH + distanceToCenter = customLength(rayPositionBlackHoleSpace);//distance to BH + vec3 blackholeDir = -rayPositionBlackHoleSpace / distanceToCenter;//direction to BH float distanceToCenter2 = distanceToCenter * distanceToCenter; - projectedPosition = projectOnPlane(positionBHS, object_rotationAxis); - projectedDistance = length(projectedPosition - positionBHS); + projectedPosition = projectOnPlane(rayPositionBlackHoleSpace, object_rotationAxis); + projectedDistance = length(projectedPosition - rayPositionBlackHoleSpace); projectedRayDir = projectOnPlane(rayDir, object_rotationAxis); rayDirProjectedDistance = length(projectedRayDir - rayDir); @@ -209,7 +209,7 @@ void main() { stepSize = min(stepSize, min(farLimit, closeLimit)); rayDir = bendRay(rayDir, blackholeDir, distanceToCenter2, maxBendDistance, stepSize); - positionBHS += stepSize * rayDir; + rayPositionBlackHoleSpace += stepSize * rayDir; //TODO: improve glow //glow += vec4(1.2,1.1,1, 1.0) * (0.2 * (object_radius / distanceToCenter2) * stepSize * clamp(distanceToCenter / object_radius - 1.2, 0.0, 1.0)); //adds fairly cheap glow @@ -223,8 +223,8 @@ void main() { break; } else if (projectedDistance <= accretionDiskHeight) { //ray hit accretion disk //FIXME: Break when rotate around edge of disk - vec4 diskCol = raymarchDisk(rayDir, positionBHS);//render disk - positionBHS += accretionDiskHeight * rayDir / rayDirProjectedDistance;// we get out of the disk + vec4 diskCol = raymarchDisk(rayDir, rayPositionBlackHoleSpace);//render disk + rayPositionBlackHoleSpace += accretionDiskHeight * rayDir / rayDirProjectedDistance;// we get out of the disk col += diskCol * (1.0 - col.a); } @@ -233,7 +233,7 @@ void main() { } // getting the screen coordinate of the end of the bended ray - vec2 uv = uvFromWorld(positionBHS, camera_projection, camera_view); + vec2 uv = uvFromWorld(rayPositionBlackHoleSpace, camera_projection, camera_view); // check if there is an object occlusion vec3 pixelWorldPositionEndRay = worldFromUV(uv, camera_inverseProjection, camera_inverseView);// the pixel position in world space (near plane) vec3 rayDirToEndRay = normalize(pixelWorldPositionEndRay - camera_position);// normalized direction of the ray @@ -249,8 +249,10 @@ void main() { float maximumDistanceEndRay = length(closestPointEndRay);// the maxium ray length due to occlusion float BHDistance = length(camera_position - object_position); + bool behindBH = dot(closestPointEndRay - camera_position, closestPointEndRay - object_position) >= 0.0; + vec4 bg = vec4(0.0); - if(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0 && maximumDistanceEndRay > BHDistance - object_radius) { + if(uv.x >= 0.0 && uv.x <= 1.0 && uv.y >= 0.0 && uv.y <= 1.0 && behindBH) { bg = texture2D(textureSampler, uv); } else { rayDir = vec3(starfieldRotation * vec4(rayDir, 1.0)); diff --git a/src/styles/pauseMenu/index.scss b/src/styles/pauseMenu/index.scss index 43d35a1f6..0cc616bb0 100644 --- a/src/styles/pauseMenu/index.scss +++ b/src/styles/pauseMenu/index.scss @@ -16,16 +16,12 @@ // along with this program. If not, see . #pauseMask { - z-index: 40; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - background: transparent; - backdrop-filter: blur(4px); -} - -#pauseMenu { - z-index: 41; + z-index: 40; + position: absolute; + top: 0; + left: 0; + bottom: 0; + right: 0; + background: rgba(0, 0, 0, 0.2); + backdrop-filter: blur(4px); } \ No newline at end of file diff --git a/src/ts/alphaTestis.ts b/src/ts/alphaTestis.ts index 03b5c84a3..72b60b0e4 100644 --- a/src/ts/alphaTestis.ts +++ b/src/ts/alphaTestis.ts @@ -35,11 +35,9 @@ import { RingsUniforms } from "./postProcesses/rings/ringsUniform"; import { getMoonSeed } from "./planets/common"; import { SystemSeed } from "./utils/systemSeed"; -const engine = new CosmosJourneyer(); +const engine = await CosmosJourneyer.CreateAsync(); -await engine.setup(); - -const starSystemView = engine.getStarSystemView(); +const starSystemView = engine.starSystemView; const spaceshipController = starSystemView.getSpaceshipControls(); @@ -54,7 +52,7 @@ const starSystemSeed = new SystemSeed(0, 0, 0, 0); const starSystem = new StarSystemController(starSystemSeed, starSystemView.scene); starSystem.model.setName("Alpha Testis"); -engine.getStarSystemView().setStarSystem(starSystem, false); +starSystemView.setStarSystem(starSystem, false); const sunModel = new StarModel(0.51); const sun = StarSystemHelper.makeStar(starSystem, sunModel); diff --git a/src/ts/assets.ts b/src/ts/assets.ts index 7dc40980e..7ff397b63 100644 --- a/src/ts/assets.ts +++ b/src/ts/assets.ts @@ -71,6 +71,8 @@ import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder"; import { ProceduralTexture } from "@babylonjs/core/Materials/Textures/Procedurals/proceduralTexture"; import { createButterfly } from "./proceduralAssets/butterfly/butterfly"; import { createGrassBlade } from "./proceduralAssets/grass/grassBlade"; +import { ButterflyMaterial } from "./proceduralAssets/butterfly/butterflyMaterial"; +import { GrassMaterial } from "./proceduralAssets/grass/grassMaterial"; export class Assets { static IS_READY = false; @@ -114,6 +116,9 @@ export class Assets { public static Butterfly: Mesh; public static GrassBlade: Mesh; + public static ButterflyMaterial: ButterflyMaterial; + public static GrassMaterial: GrassMaterial; + public static OuchSound: Sound; public static EngineRunningSound: Sound; @@ -212,6 +217,7 @@ export class Assets { Assets.Rock.position.y = 0.1; Assets.Rock.scaling.scaleInPlace(0.2); Assets.Rock.bakeCurrentTransformIntoVertices(); + Assets.Rock.checkCollisions = true; Assets.Rock.isVisible = false; console.log("Rock loaded"); @@ -235,6 +241,7 @@ export class Assets { Assets.Tree.position.y = -1; Assets.Tree.scaling.scaleInPlace(3); Assets.Tree.bakeCurrentTransformIntoVertices(); + Assets.Tree.checkCollisions = true; const treeMaterial = new StandardMaterial("treeMaterial", scene); @@ -255,8 +262,12 @@ export class Assets { }; Assets.Butterfly = createButterfly(scene); + Assets.ButterflyMaterial = new ButterflyMaterial(scene); + Assets.Butterfly.material = Assets.ButterflyMaterial; Assets.GrassBlade = createGrassBlade(scene, 3); + Assets.GrassMaterial = new GrassMaterial(scene); + Assets.GrassBlade.material = Assets.GrassMaterial; const ouchSoundTask = Assets.manager.addBinaryFileTask("ouchSoundTask", ouchSound); ouchSoundTask.onSuccess = function (task) { diff --git a/src/ts/blackHoleDemo.ts b/src/ts/blackHoleDemo.ts index 9bca9d067..568394f01 100644 --- a/src/ts/blackHoleDemo.ts +++ b/src/ts/blackHoleDemo.ts @@ -24,11 +24,9 @@ import { CosmosJourneyer } from "./cosmosJourneyer"; import { StarSystemHelper } from "./starSystem/starSystemHelper"; import { SystemSeed } from "./utils/systemSeed"; -const engine = new CosmosJourneyer(); +const engine = await CosmosJourneyer.CreateAsync(); -await engine.setup(); - -const starSystemView = engine.getStarSystemView(); +const starSystemView = engine.starSystemView; const scene = starSystemView.scene; diff --git a/src/ts/cosmosJourneyer.ts b/src/ts/cosmosJourneyer.ts index f6ab36e8c..302feaa9f 100644 --- a/src/ts/cosmosJourneyer.ts +++ b/src/ts/cosmosJourneyer.ts @@ -40,10 +40,11 @@ import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { Quaternion } from "@babylonjs/core/Maths/math"; import { setRotationQuaternion } from "./uberCore/transforms/basicTransform"; import { ShipControls } from "./spaceship/shipControls"; -import { PhysicsMotionType } from "@babylonjs/core"; -import { setMaxLinVel } from "./utils/havok"; +import { encodeBase64 } from "./utils/base64"; +import { UniverseCoordinates } from "./saveFile/universeCoordinates"; enum EngineState { + UNINITIALIZED, RUNNING, PAUSED } @@ -54,40 +55,71 @@ enum EngineState { * It also handles the pause menu. */ export class CosmosJourneyer { - private readonly pauseMenu: PauseMenu; - private videoRecorder: VideoRecorder | null = null; + readonly engine: Engine; + + readonly starSystemView: StarSystemView; + readonly starMap: StarMap; - readonly canvas: HTMLCanvasElement; - private engine: Engine | null = null; + readonly mainMenu: MainMenu; + readonly pauseMenu: PauseMenu; - private mainMenu: MainMenu | null = null; - private starSystemView: StarSystemView | null = null; - private starMap: StarMap | null = null; + private activeScene: Scene; - private activeScene: Scene | null = null; + private state = EngineState.UNINITIALIZED; - private state = EngineState.RUNNING; + private videoRecorder: VideoRecorder | null = null; readonly onToggleStarMapObservable = new Observable(); - constructor() { + private constructor(engine: Engine, starSystemView: StarSystemView, starMap: StarMap) { + this.engine = engine; + + this.starSystemView = starSystemView; + this.starMap = starMap; + this.starMap.onWarpObservable.add((seed: SystemSeed) => { + this.starSystemView.setStarSystem(new StarSystemController(seed, this.starSystemView.scene), true); + this.starSystemView.initStarSystem(); + this.toggleStarMap(); + + const activeControls = this.starSystemView.scene.getActiveController(); + if (activeControls instanceof ShipControls) { + activeControls.spaceship.enableWarpDrive(); + activeControls.thirdPersonCamera.radius = 30; + } + }); + + // Init the active scene + this.starMap.scene.detachControl(); + this.starSystemView.scene.attachControl(); + this.activeScene = this.starSystemView.scene; + + this.mainMenu = new MainMenu(starSystemView); + this.mainMenu.onStartObservable.add(() => { + this.starMap.setCurrentStarSystem(this.starSystemView.getStarSystem().model.seed); + this.starSystemView.switchToSpaceshipControls(); + this.starSystemView.getSpaceshipControls().spaceship.enableWarpDrive(); + this.starSystemView.showUI(); + this.starSystemView.ui.setEnabled(true); + }); + + this.mainMenu.onLoadSaveObservable.add((saveData: SaveFileData) => { + this.loadSaveData(saveData); + }); + this.pauseMenu = new PauseMenu(); this.pauseMenu.onResume.add(() => this.resume()); this.pauseMenu.onScreenshot.add(() => this.takeScreenshot()); this.pauseMenu.onShare.add(() => { const saveData = this.generateSaveData(); - const starSystem = saveData.starSystem; - const payload = `starMapX=${starSystem.starSectorX}&starMapY=${starSystem.starSectorY}&starMapZ=${starSystem.starSectorZ}&index=${starSystem.starSectorIndex}&objectIndex=${saveData.nearestOrbitalObjectIndex}&positionX=${saveData.positionX}&positionY=${saveData.positionY}&positionZ=${saveData.positionZ}&rotationQuaternionX=${saveData.rotationQuaternionX}&rotationQuaternionY=${saveData.rotationQuaternionY}&rotationQuaternionZ=${saveData.rotationQuaternionZ}&rotationQuaternionW=${saveData.rotationQuaternionW}`; - const url = new URL(`https://barthpaleologue.github.io/CosmosJourneyer/random.html?${payload}`); + const urlData = encodeBase64(JSON.stringify(saveData.universeCoordinates)); + + const payload = `universeCoordinates=${urlData}`; + const url = new URL(`https://barthpaleologue.github.io/CosmosJourneyer/?${payload}`); navigator.clipboard.writeText(url.toString()).then(() => console.log("Copied to clipboard")); }); this.pauseMenu.onSave.add(() => this.downloadSaveFile()); - this.canvas = document.getElementById("renderer") as HTMLCanvasElement; - this.canvas.width = window.innerWidth; - this.canvas.height = window.innerHeight; - window.addEventListener("blur", () => { if (!this.mainMenu?.isVisible()) this.pause(); }); @@ -114,68 +146,47 @@ export class CosmosJourneyer { * Creates the engine and the scenes and loads the assets async * @returns A promise that resolves when the engine and the scenes are created and the assets are loaded */ - public async setup(): Promise { + public static async CreateAsync(): Promise { + const canvas = document.getElementById("renderer") as HTMLCanvasElement; + canvas.width = window.innerWidth; + canvas.height = window.innerHeight; + // Init BabylonJS engine (use webgpu if ?webgpu is in the url) - this.engine = window.location.search.includes("webgpu") - ? await EngineFactory.CreateAsync(this.canvas, { + const engine = window.location.search.includes("webgpu") + ? await EngineFactory.CreateAsync(canvas, { twgslOptions: { wasmPath: new URL("./utils/TWGSL/twgsl.wasm", import.meta.url).href, jsPath: new URL("./utils/TWGSL/twgsl.js", import.meta.url).href } }) - : new Engine(this.canvas, true, { - // the preserveDrawingBuffer option is required for the screenshot feature to work - preserveDrawingBuffer: true - }); + : new Engine(canvas, true, { + // the preserveDrawingBuffer option is required for the screenshot feature to work + preserveDrawingBuffer: true + }); - //this.engine = new Engine(this.canvas); //await EngineFactory.CreateAsync(this.canvas, { enableAllFeatures: true }); - this.engine.useReverseDepthBuffer = true; - this.engine.loadingScreen.displayLoadingUI(); + engine.useReverseDepthBuffer = true; + engine.loadingScreen.displayLoadingUI(); window.addEventListener("resize", () => { - this.getEngine().resize(true); + engine.resize(true); }); // Log informations about the gpu and the api used - console.log(`API: ${this.engine.isWebGPU ? "WebGPU" : "WebGL" + this.engine.webGLVersion}`); - console.log(`GPU detected: ${this.engine.getGlInfo().renderer}`); + console.log(`API: ${engine.isWebGPU ? "WebGPU" : "WebGL" + engine.version}`); + console.log(`GPU detected: ${engine.getGlInfo().renderer}`); // Init Havok physics engine const havokInstance = await HavokPhysics(); console.log(`Havok initialized`); // Init starmap view - this.starMap = new StarMap(this.engine); - this.starMap.onWarpObservable.add((seed: SystemSeed) => { - this.getStarSystemView().setStarSystem(new StarSystemController(seed, this.getStarSystemView().scene), true); - this.getStarSystemView().initStarSystem(); - this.toggleStarMap(); - - const activeControls = this.getStarSystemView().scene.getActiveController(); - if (activeControls instanceof ShipControls) { - activeControls.spaceship.enableWarpDrive(); - activeControls.thirdPersonCamera.radius = 30; - } - }); + const starMap = new StarMap(engine); // Init star system view - this.starSystemView = new StarSystemView(this.engine, havokInstance); - await this.starSystemView.initAssets(); - - this.mainMenu = new MainMenu(this.starSystemView); - this.mainMenu.onStartObservable.add(() => { - this.getStarMap().setCurrentStarSystem(this.getStarSystemView().getStarSystem().model.seed); - this.getStarSystemView().switchToSpaceshipControls(); - this.getStarSystemView().getSpaceshipControls().spaceship.enableWarpDrive(); - this.getStarSystemView().showUI(); - this.getStarSystemView().ui.setEnabled(true); - }); + const starSystemView = new StarSystemView(engine, havokInstance); - this.mainMenu.onLoadSaveObservable.add((saveData: SaveFileData) => { - this.loadSaveData(saveData); - }); + await starSystemView.initAssets(); - // Init the active scene - this.activeScene = this.starSystemView.scene; + return new CosmosJourneyer(engine, starSystemView, starMap); } public pause(): void { @@ -196,81 +207,46 @@ export class CosmosJourneyer { * Inits the current star system */ public init(skipMainMenu = false): void { - if (!skipMainMenu) this.getMainMenu().init(); - this.getStarSystemView().initStarSystem(); + if (!skipMainMenu) this.mainMenu.init(); + this.starSystemView.initStarSystem(); - this.getEngine().runRenderLoop(() => { + this.engine.runRenderLoop(() => { if (this.isPaused()) return; this.getActiveScene().render(); }); - } - - /** - * Registers a callback to be called before the star system scene is rendered - * @param callback the callback to be called before the star system scene is rendered - */ - public registerStarSystemUpdateCallback(callback: () => void): void { - this.getStarSystemView().scene.onBeforeRenderObservable.add(callback); - } - - public getMainMenu(): MainMenu { - if (this.mainMenu === null) throw new Error("Main menu is null"); - return this.mainMenu; - } - - public getStarSystemView(): StarSystemView { - if (this.starSystemView === null) throw new Error("Star system view is null"); - return this.starSystemView; - } - - public getStarMap(): StarMap { - if (this.starMap === null) throw new Error("Star map is null"); - return this.starMap; + this.state = EngineState.RUNNING; } /** * Toggles the star map - * @throws Error if the star map is null */ public toggleStarMap(): void { - if (this.activeScene === this.getStarSystemView().scene) { - this.getStarSystemView().unZoom(() => { - if (this.activeScene !== null) this.activeScene.detachControl(); - this.getStarMap().scene.attachControl(); - const starMap = this.getStarMap(); + if (this.activeScene === this.starSystemView.scene) { + this.starSystemView.unZoom(() => { + this.activeScene.detachControl(); + this.starMap.scene.attachControl(); + const starMap = this.starMap; this.activeScene = starMap.scene; starMap.focusOnCurrentSystem(); }); } else { - if (this.activeScene !== null) this.activeScene.detachControl(); - this.getStarSystemView().scene.attachControl(); - this.activeScene = this.getStarSystemView().scene; - this.getStarSystemView().showUI(); + this.activeScene.detachControl(); + this.starSystemView.scene.attachControl(); + this.activeScene = this.starSystemView.scene; + this.starSystemView.showUI(); } - this.onToggleStarMapObservable.notifyObservers(this.activeScene === this.getStarMap().scene); + this.onToggleStarMapObservable.notifyObservers(this.activeScene === this.starMap.scene); } /** * Returns the active scene (star system or star map) * @returns the active scene (star system or star map) - * @throws Error if the active scene is null */ public getActiveScene(): Scene { - if (this.activeScene === null) throw new Error("Active scene is null"); return this.activeScene; } - /** - * Returns the BabylonJS engine - * @returns the BabylonJS engine - * @throws Error if the engine is null - */ - public getEngine(): Engine { - if (this.engine === null) throw new Error("Engine is null"); - return this.engine; - } - /** * Takes a screenshot of the current scene. By default, the screenshot is taken at a 4x the resolution of the canvas * @param precision The resolution multiplier of the screenshot @@ -278,17 +254,17 @@ export class CosmosJourneyer { public takeScreenshot(precision = 4): void { const camera = this.getActiveScene().activeCamera; if (camera === null) throw new Error("Cannot take screenshot: camera is null"); - Tools.CreateScreenshot(this.getEngine(), camera, { precision: precision }); + Tools.CreateScreenshot(this.engine, camera, { precision: precision }); } public takeVideoCapture(): void { - if (!VideoRecorder.IsSupported(this.getEngine())) { + if (!VideoRecorder.IsSupported(this.engine)) { console.warn("Your browser does not support video recording!"); return; } if (this.videoRecorder === null) { - this.videoRecorder = new VideoRecorder(this.getEngine(), { + this.videoRecorder = new VideoRecorder(this.engine, { fps: 60, recordChunckSize: 3000000, mimeType: "video/webm;codecs=h264" @@ -306,7 +282,7 @@ export class CosmosJourneyer { * Generates a save file data object from the current star system and the player's position */ public generateSaveData(): SaveFileData { - const currentStarSystem = this.getStarSystemView().getStarSystem(); + const currentStarSystem = this.starSystemView.getStarSystem(); const seed = currentStarSystem.model.seed; // Finding the index of the nearest orbital object @@ -315,31 +291,28 @@ export class CosmosJourneyer { if (nearestOrbitalObjectIndex === -1) throw new Error("Nearest orbital object not found"); // Finding the position of the player in the nearest orbital object's frame of reference - const currentWorldPosition = this.getStarSystemView().scene.getActiveController().getTransform().getAbsolutePosition(); + const currentWorldPosition = this.starSystemView.scene.getActiveController().getTransform().getAbsolutePosition(); const nearestOrbitalObjectInverseWorld = nearestOrbitalObject.getTransform().getWorldMatrix().clone().invert(); const currentLocalPosition = Vector3.TransformCoordinates(currentWorldPosition, nearestOrbitalObjectInverseWorld); // Finding the rotation of the player in the nearest orbital object's frame of reference - const currentWorldRotation = this.getStarSystemView().scene.getActiveController().getTransform().absoluteRotationQuaternion; + const currentWorldRotation = this.starSystemView.scene.getActiveController().getTransform().absoluteRotationQuaternion; const nearestOrbitalObjectInverseRotation = nearestOrbitalObject.getTransform().absoluteRotationQuaternion.clone().invert(); const currentLocalRotation = currentWorldRotation.multiply(nearestOrbitalObjectInverseRotation); return { version: projectInfo.version, - starSystem: { - starSectorX: seed.starSectorX, - starSectorY: seed.starSectorY, - starSectorZ: seed.starSectorZ, - starSectorIndex: seed.index - }, - nearestOrbitalObjectIndex: nearestOrbitalObjectIndex, - positionX: currentLocalPosition.x, - positionY: currentLocalPosition.y, - positionZ: currentLocalPosition.z, - rotationQuaternionX: currentLocalRotation.x, - rotationQuaternionY: currentLocalRotation.y, - rotationQuaternionZ: currentLocalRotation.z, - rotationQuaternionW: currentLocalRotation.w + universeCoordinates: { + starSystem: seed.serialize(), + nearestOrbitalObjectIndex: nearestOrbitalObjectIndex, + positionX: currentLocalPosition.x, + positionY: currentLocalPosition.y, + positionZ: currentLocalPosition.z, + rotationQuaternionX: currentLocalRotation.x, + rotationQuaternionY: currentLocalRotation.y, + rotationQuaternionZ: currentLocalRotation.z, + rotationQuaternionW: currentLocalRotation.w + } }; } @@ -358,45 +331,56 @@ export class CosmosJourneyer { /** * Loads a save file and apply it. This will generate the requested star system and position the player at the requested position around the requested orbital object. + * This will perform engine initialization if the engine is not initialized. * @param saveData The save file data to load */ public loadSaveData(saveData: SaveFileData): void { - const seed = new SystemSeed(saveData.starSystem.starSectorX, saveData.starSystem.starSectorY, saveData.starSystem.starSectorZ, saveData.starSystem.starSectorIndex); + this.loadUniverseCoordinates(saveData.universeCoordinates); + } + + /** + * Loads universe coordinates and apply them. This will generate the requested star system and position the player at the requested position around the requested orbital object. + * This will perform engine initialization if the engine is not initialized. + * @param universeCoordinates The universe coordinates to load + */ + public loadUniverseCoordinates(universeCoordinates: UniverseCoordinates): void { + const seed = SystemSeed.Deserialize(universeCoordinates.starSystem); - this.getStarMap().setCurrentStarSystem(seed); - this.getStarSystemView().setStarSystem(new StarSystemController(seed, this.getStarSystemView().scene), true); + this.starMap.setCurrentStarSystem(seed); + this.starSystemView.setStarSystem(new StarSystemController(seed, this.starSystemView.scene), true); - this.getStarSystemView().onInitStarSystem.addOnce(() => { - this.getStarSystemView().switchToSpaceshipControls(); + this.starSystemView.onInitStarSystem.addOnce(() => { + this.starSystemView.switchToSpaceshipControls(); - this.getStarSystemView().ui.setEnabled(true); - this.getStarSystemView().showUI(); + this.starSystemView.ui.setEnabled(true); + this.starSystemView.showUI(); - const playerTransform = this.getStarSystemView().scene.getActiveController().getTransform(); + const playerTransform = this.starSystemView.scene.getActiveController().getTransform(); - const nearestOrbitalObject = this.getStarSystemView().getStarSystem().getOrbitalObjects()[saveData.nearestOrbitalObjectIndex]; + const nearestOrbitalObject = this.starSystemView.getStarSystem().getOrbitalObjects()[universeCoordinates.nearestOrbitalObjectIndex]; const nearestOrbitalObjectWorld = nearestOrbitalObject.getTransform().getWorldMatrix(); - const currentLocalPosition = new Vector3(saveData.positionX, saveData.positionY, saveData.positionZ); + const currentLocalPosition = new Vector3(universeCoordinates.positionX, universeCoordinates.positionY, universeCoordinates.positionZ); const currentWorldPosition = Vector3.TransformCoordinates(currentLocalPosition, nearestOrbitalObjectWorld); playerTransform.setAbsolutePosition(currentWorldPosition); const nearestOrbitalObjectWorldRotation = nearestOrbitalObject.getTransform().absoluteRotationQuaternion; const currentLocalRotationQuaternion = new Quaternion( - saveData.rotationQuaternionX, - saveData.rotationQuaternionY, - saveData.rotationQuaternionZ, - saveData.rotationQuaternionW + universeCoordinates.rotationQuaternionX, + universeCoordinates.rotationQuaternionY, + universeCoordinates.rotationQuaternionZ, + universeCoordinates.rotationQuaternionW ); const currentWorldRotationQuaternion = currentLocalRotationQuaternion.multiply(nearestOrbitalObjectWorldRotation); setRotationQuaternion(playerTransform, currentWorldRotationQuaternion); // updates camera position - this.getStarSystemView().getSpaceshipControls().getActiveCamera().getViewMatrix(true); + this.starSystemView.getSpaceshipControls().getActiveCamera().getViewMatrix(true); // re-centers the star system - this.getStarSystemView().getStarSystem().applyFloatingOrigin(); + this.starSystemView.getStarSystem().applyFloatingOrigin(); }); - this.getStarSystemView().initStarSystem(); + if (this.state === EngineState.UNINITIALIZED) this.init(true); + else this.starSystemView.initStarSystem(); } } diff --git a/src/ts/defaultController/defaultControls.ts b/src/ts/defaultController/defaultControls.ts index 93edf92bb..73ce8ee89 100644 --- a/src/ts/defaultController/defaultControls.ts +++ b/src/ts/defaultController/defaultControls.ts @@ -19,7 +19,16 @@ import { Controls } from "../uberCore/controls"; import { Scene } from "@babylonjs/core/scene"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { TransformNode } from "@babylonjs/core/Meshes"; -import { getForwardDirection, getRightDirection, getUpwardDirection, pitch, roll, translate, yaw } from "../uberCore/transforms/basicTransform"; +import { + getForwardDirection, + getRightDirection, + getUpwardDirection, + pitch, + roll, + setRotationQuaternion, + translate, + yaw +} from "../uberCore/transforms/basicTransform"; import { Input } from "../inputs/input"; import { Camera } from "@babylonjs/core/Cameras/camera"; import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera"; @@ -38,6 +47,7 @@ export class DefaultControls implements Controls { constructor(scene: Scene) { this.transform = new TransformNode("playerController", scene); + setRotationQuaternion(this.getTransform(), Quaternion.Identity()); this.camera = new FreeCamera("firstPersonCamera", Vector3.Zero(), scene); this.camera.parent = this.transform; diff --git a/src/ts/index.ts b/src/ts/index.ts index cb66f4439..4bccb6d95 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -19,17 +19,28 @@ import "../styles/index.scss"; import { CosmosJourneyer } from "./cosmosJourneyer"; import { getForwardDirection, getRotationQuaternion, setRotationQuaternion, translate } from "./uberCore/transforms/basicTransform"; +import { decodeBase64 } from "./utils/base64"; +import { isJsonStringValidUniverseCoordinates } from "./saveFile/universeCoordinates"; -const engine = new CosmosJourneyer(); +const engine = await CosmosJourneyer.CreateAsync(); -await engine.setup(); +const starSystemView = engine.starSystemView; -const starSystemView = engine.getStarSystemView(); -engine.init(); +const urlParams = new URLSearchParams(window.location.search); +const universeCoordinatesString = urlParams.get("universeCoordinates"); + +if(universeCoordinatesString !== null) { + const jsonString = decodeBase64(universeCoordinatesString); + if(!isJsonStringValidUniverseCoordinates(jsonString)) { + alert("Invalid universe coordinates"); + } + engine.loadUniverseCoordinates(JSON.parse(jsonString)); +} else { + engine.init(false); +} const shipControls = starSystemView.getSpaceshipControls(); const characterController = starSystemView.getCharacterControls(); -const defaultController = starSystemView.getDefaultControls(); document.addEventListener("keydown", (e) => { if (engine.isPaused()) return; diff --git a/src/ts/mainMenu/mainMenu.ts b/src/ts/mainMenu/mainMenu.ts index de85dc922..4d1ebcd53 100644 --- a/src/ts/mainMenu/mainMenu.ts +++ b/src/ts/mainMenu/mainMenu.ts @@ -165,7 +165,7 @@ export class MainMenu { this.startAnimation(() => this.onLoadSaveObservable.notifyObservers(saveFileData)); } catch (e) { dropFileZone.classList.add("invalid"); - alert("Invalid save file"); + alert("Invalid save file. Please check your save file against the current format at https://barthpaleologue.github.io/CosmosJourneyer/docs/types/saveFile_saveFileData.SaveFileData.html\nYou can open an issue here if the issue persists: https://github.com/BarthPaleologue/CosmosJourneyer"); } }; reader.readAsText(file); diff --git a/src/ts/planets/telluricPlanet/telluricPlanet.ts b/src/ts/planets/telluricPlanet/telluricPlanet.ts index 552ec3e7e..6508a9cea 100644 --- a/src/ts/planets/telluricPlanet/telluricPlanet.ts +++ b/src/ts/planets/telluricPlanet/telluricPlanet.ts @@ -38,11 +38,11 @@ import { TransformNode } from "@babylonjs/core/Meshes"; import { OrbitProperties } from "../../orbit/orbitProperties"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; import { PhysicsShapeType } from "@babylonjs/core/Physics/v2/IPhysicsEnginePlugin"; -import { OrbitalObject } from "../../architecture/orbitalObject"; import { CelestialBody } from "../../architecture/celestialBody"; import { RingsUniforms } from "../../postProcesses/rings/ringsUniform"; import { OrbitalObjectPhysicalProperties } from "../../architecture/physicalProperties"; import { rotate } from "../../uberCore/transforms/basicTransform"; +import { BODY_TYPE } from "../../model/common"; export class TelluricPlanet implements Planet, Cullable { readonly name: string; @@ -60,7 +60,7 @@ export class TelluricPlanet implements Planet, Cullable { readonly postProcesses: PostProcessType[] = []; - readonly parent: OrbitalObject | null; + readonly parent: CelestialBody | null; /** * New Telluric Planet @@ -149,6 +149,9 @@ export class TelluricPlanet implements Planet, Cullable { } getTypeName(): string { + if (this.parent?.model.bodyType === BODY_TYPE.TELLURIC_PLANET || this.parent?.model.bodyType === BODY_TYPE.GAS_PLANET) { + return "Telluric Moon"; + } return "Telluric Planet"; } diff --git a/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts b/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts index 4f8334338..bcc6264ac 100644 --- a/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts +++ b/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts @@ -32,6 +32,8 @@ import { DeleteSemaphore } from "./deleteSemaphore"; import { UberScene } from "../../../../uberCore/uberScene"; import { getRotationQuaternion } from "../../../../uberCore/transforms/basicTransform"; import { ChunkForge } from "./chunkForge"; +import { PhysicsRaycastResult } from "@babylonjs/core/Physics/physicsRaycastResult"; +import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2"; /** * A quadTree is defined recursively @@ -180,7 +182,7 @@ export class ChunkTree { const distanceToNodeSquared = Vector3.DistanceSquared(chunkApproxPosition, observerPositionW); const subdivisionDistanceThreshold = Settings.CHUNK_RENDER_DISTANCE_MULTIPLIER * (this.rootChunkLength / 2 ** walked.length); - const deletionDistanceThreshold = 10e3 + Settings.CHUNK_RENDER_DISTANCE_MULTIPLIER * (this.rootChunkLength / 2 ** (walked.length - 1)); + const deletionDistanceThreshold = 15e3 + 1.1 * Settings.CHUNK_RENDER_DISTANCE_MULTIPLIER * (this.rootChunkLength / 2 ** (walked.length - 1)); // the 1.5 is to avoid creation/deletion oscillations if (distanceToNodeSquared > deletionDistanceThreshold ** 2 && walked.length >= this.minDepth && tree instanceof Array) { diff --git a/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts b/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts index 781b8497a..1d9b0e311 100644 --- a/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts +++ b/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts @@ -38,6 +38,7 @@ import { LockConstraint } from "@babylonjs/core/Physics/v2/physicsConstraint"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; import { Transformable } from "../../../../architecture/transformable"; import { CollisionMask } from "../../../../settings"; +import { InstancePatch } from "../instancePatch/instancePatch"; export class PlanetChunk implements Transformable, BoundingSphere { public readonly mesh: Mesh; @@ -151,12 +152,12 @@ export class PlanetChunk implements Transformable, BoundingSphere { if (instancesMatrixBuffer.length === 0) return; - const rockPatch = new ThinInstancePatch(this.parent, randomDownSample(alignedInstancesMatrixBuffer, 3200)); + const rockPatch = new InstancePatch(this.parent, randomDownSample(alignedInstancesMatrixBuffer, 3200)); rockPatch.createInstances(Assets.Rock); this.instancePatches.push(rockPatch); if (this.planetModel.physicalProperties.pressure > 0 && this.planetModel.physicalProperties.oceanLevel > 0) { - const treePatch = new ThinInstancePatch(this.parent, randomDownSample(instancesMatrixBuffer, 4800)); + const treePatch = new InstancePatch(this.parent, randomDownSample(instancesMatrixBuffer, 4800)); treePatch.createInstances(Assets.Tree); this.instancePatches.push(treePatch); diff --git a/src/ts/planets/telluricPlanet/terrain/instancePatch/instancePatch.ts b/src/ts/planets/telluricPlanet/terrain/instancePatch/instancePatch.ts index 3f0157b0f..353492fc6 100644 --- a/src/ts/planets/telluricPlanet/terrain/instancePatch/instancePatch.ts +++ b/src/ts/planets/telluricPlanet/terrain/instancePatch/instancePatch.ts @@ -24,15 +24,16 @@ import { TransformNode } from "@babylonjs/core/Meshes/transformNode"; export class InstancePatch implements IPatch { private baseMesh: Mesh | null = null; - readonly position: Vector3; + + readonly parent: TransformNode; readonly instances: InstancedMesh[] = []; private positions: Vector3[] = []; private rotations: Quaternion[] = []; private scalings: Vector3[] = []; - constructor(position: Vector3, matrixBuffer: Float32Array) { - this.position = position; + constructor(parent: TransformNode, matrixBuffer: Float32Array) { + this.parent = parent; // decompose matrix buffer into position, rotation and scaling for (let i = 0; i < matrixBuffer.length; i += 16) { @@ -58,9 +59,9 @@ export class InstancePatch implements IPatch { this.baseMesh = null; } - public static CreateSquare(position: Vector3, size: number, resolution: number) { + public static CreateSquare(parent: TransformNode, position: Vector3, size: number, resolution: number) { const buffer = createSquareMatrixBuffer(position, size, resolution); - return new InstancePatch(position, buffer); + return new InstancePatch(parent, buffer); } public createInstances(baseMesh: TransformNode): void { @@ -79,6 +80,8 @@ export class InstancePatch implements IPatch { instance.scaling.copyFrom(this.scalings[i]); this.instances.push(instance); + instance.parent = this.parent; + instance.checkCollisions = baseMesh.checkCollisions; } } @@ -93,10 +96,6 @@ export class InstancePatch implements IPatch { return this.baseMesh.instances.length; } - public getPosition(): Vector3 { - return this.position; - } - public dispose() { this.clearInstances(); if (this.baseMesh !== null) this.baseMesh.dispose(); diff --git a/src/ts/planets/telluricPlanet/terrain/instancePatch/thinInstancePatch.ts b/src/ts/planets/telluricPlanet/terrain/instancePatch/thinInstancePatch.ts index f36258cae..891a06387 100644 --- a/src/ts/planets/telluricPlanet/terrain/instancePatch/thinInstancePatch.ts +++ b/src/ts/planets/telluricPlanet/terrain/instancePatch/thinInstancePatch.ts @@ -67,7 +67,7 @@ export class ThinInstancePatch implements IPatch { }); this.baseMesh.isVisible = true; - this.baseMesh.thinInstanceSetBuffer("matrix", this.matrixBuffer, 16); + this.baseMesh.thinInstanceSetBuffer("matrix", this.matrixBuffer, 16, false); } public syncWithParent(): void { diff --git a/src/ts/postProcesses/postProcessManager.ts b/src/ts/postProcesses/postProcessManager.ts index 436aeea78..e1e19860b 100644 --- a/src/ts/postProcesses/postProcessManager.ts +++ b/src/ts/postProcesses/postProcessManager.ts @@ -16,7 +16,6 @@ // along with this program. If not, see . import { UberScene } from "../uberCore/uberScene"; -import { UberRenderingPipeline } from "../uberCore/uberRenderingPipeline"; import { OceanPostProcess } from "./oceanPostProcess"; import { TelluricPlanet } from "../planets/telluricPlanet/telluricPlanet"; import { FlatCloudsPostProcess } from "./clouds/flatCloudsPostProcess"; @@ -33,7 +32,6 @@ import { CloudsPostProcess } from "./volumetricCloudsPostProcess"; import { Engine } from "@babylonjs/core/Engines/engine"; import { FxaaPostProcess } from "@babylonjs/core/PostProcesses/fxaaPostProcess"; import { PostProcessRenderEffect } from "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderEffect"; -import { BloomEffect } from "@babylonjs/core/PostProcesses/bloomEffect"; import { Texture } from "@babylonjs/core/Materials/Textures/texture"; import "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipelineManagerSceneComponent"; import { PostProcessType } from "./postProcessTypes"; @@ -50,6 +48,8 @@ import { BlackHole } from "../stellarObjects/blackHole/blackHole"; import { NeutronStar } from "../stellarObjects/neutronStar/neutronStar"; import { CelestialBody } from "../architecture/celestialBody"; import { StellarObject } from "../architecture/stellarObject"; +import { PostProcessRenderPipelineManager } from "@babylonjs/core"; +import { PostProcessRenderPipeline } from "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipeline"; /** * The order in which the post processes are rendered when away from a planet @@ -88,9 +88,11 @@ export class PostProcessManager { private readonly engine: Engine; private readonly scene: UberScene; - private readonly spaceRenderingPipeline: UberRenderingPipeline; - private readonly surfaceRenderingPipeline: UberRenderingPipeline; - private currentRenderingPipeline: UberRenderingPipeline; + private readonly renderingPipelineManager: PostProcessRenderPipelineManager; + + private readonly spaceRenderingPipeline: PostProcessRenderPipeline; + private readonly surfaceRenderingPipeline: PostProcessRenderPipeline; + private currentRenderingPipeline: PostProcessRenderPipeline; private currentRenderingOrder: PostProcessType[] = spaceRenderingOrder; @@ -142,6 +144,8 @@ export class PostProcessManager { this.scene = scene; this.engine = scene.getEngine(); + this.renderingPipelineManager = scene.postProcessRenderPipelineManager; + this.colorCorrection = new ColorCorrection("colorCorrection", scene.getEngine()); this.colorCorrection.exposure = 1.5; this.colorCorrection.gamma = 1.0; @@ -156,11 +160,11 @@ export class PostProcessManager { return [this.fxaa]; }); - this.spaceRenderingPipeline = new UberRenderingPipeline("space", scene.getEngine()); - scene.postProcessRenderPipelineManager.addPipeline(this.spaceRenderingPipeline); + this.spaceRenderingPipeline = new PostProcessRenderPipeline(scene.getEngine(), "spaceRenderingPipeline"); + this.renderingPipelineManager.addPipeline(this.spaceRenderingPipeline); - this.surfaceRenderingPipeline = new UberRenderingPipeline("surface", scene.getEngine()); - scene.postProcessRenderPipelineManager.addPipeline(this.surfaceRenderingPipeline); + this.surfaceRenderingPipeline = new PostProcessRenderPipeline(scene.getEngine(), "surfaceRenderingPipeline"); + this.renderingPipelineManager.addPipeline(this.surfaceRenderingPipeline); this.currentRenderingPipeline = this.spaceRenderingPipeline; @@ -337,7 +341,7 @@ export class PostProcessManager { public setSpaceOrder() { if (this.currentRenderingPipeline === this.spaceRenderingPipeline) return; - this.surfaceRenderingPipeline.detachCamera(this.scene.getActiveCamera()); + this.renderingPipelineManager.detachCamerasFromRenderPipeline(this.surfaceRenderingPipeline.name, [this.scene.getActiveCamera()]); this.currentRenderingPipeline = this.spaceRenderingPipeline; this.currentRenderingOrder = spaceRenderingOrder; this.init(); @@ -345,7 +349,7 @@ export class PostProcessManager { public setSurfaceOrder() { if (this.currentRenderingPipeline === this.surfaceRenderingPipeline) return; - this.spaceRenderingPipeline.detachCamera(this.scene.getActiveCamera()); + this.renderingPipelineManager.detachCamerasFromRenderPipeline(this.spaceRenderingPipeline.name, [this.scene.getActiveCamera()]); this.currentRenderingPipeline = this.surfaceRenderingPipeline; this.currentRenderingOrder = surfaceRenderingOrder; this.init(); @@ -466,7 +470,7 @@ export class PostProcessManager { //this.currentRenderingPipeline.addEffect(this.bloomRenderEffect); this.currentRenderingPipeline.addEffect(this.colorCorrectionRenderEffect); - this.currentRenderingPipeline.attachToCamera(this.scene.getActiveCamera()); + this.renderingPipelineManager.attachCamerasToRenderPipeline(this.currentRenderingPipeline.name, [this.scene.getActiveCamera()]); } /** @@ -486,6 +490,9 @@ export class PostProcessManager { this.colorCorrection.dispose(); this.fxaa.dispose(); + this.surfaceRenderingPipeline._detachCameras(this.surfaceRenderingPipeline.cameras); + this.spaceRenderingPipeline._detachCameras(this.spaceRenderingPipeline.cameras); + this.surfaceRenderingPipeline.dispose(); this.spaceRenderingPipeline.dispose(); } diff --git a/src/ts/proceduralAssets/butterfly/butterfly.ts b/src/ts/proceduralAssets/butterfly/butterfly.ts index ab2b63802..1ebfef3f9 100644 --- a/src/ts/proceduralAssets/butterfly/butterfly.ts +++ b/src/ts/proceduralAssets/butterfly/butterfly.ts @@ -18,7 +18,6 @@ import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData"; import { Mesh } from "@babylonjs/core/Meshes/mesh"; import { Scene } from "@babylonjs/core/scene"; -import { createButterflyMaterial } from "./butterflyMaterial"; export function createButterfly(scene: Scene) { const positions = new Float32Array(6 * 3); @@ -103,7 +102,5 @@ export function createButterfly(scene: Scene) { mesh.scaling.scaleInPlace(0.2); mesh.bakeCurrentTransformIntoVertices(); - mesh.material = createButterflyMaterial(scene); - return mesh; } diff --git a/src/ts/proceduralAssets/butterfly/butterflyMaterial.ts b/src/ts/proceduralAssets/butterfly/butterflyMaterial.ts index cd3df67dc..6600b2adb 100644 --- a/src/ts/proceduralAssets/butterfly/butterflyMaterial.ts +++ b/src/ts/proceduralAssets/butterfly/butterflyMaterial.ts @@ -23,42 +23,38 @@ import butterflyVertex from "../../../shaders/butterflyMaterial/butterflyVertex. import butterflyTexture from "../../../asset/butterfly.png"; import { Texture } from "@babylonjs/core/Materials/Textures/texture"; -import { TransformNode } from "@babylonjs/core/Meshes/transformNode"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { PointLight } from "@babylonjs/core/Lights/pointLight"; - -export function createButterflyMaterial(scene: Scene, player?: TransformNode) { - const shaderName = "butterflyMaterial"; - Effect.ShadersStore[`${shaderName}FragmentShader`] = butterflyFragment; - Effect.ShadersStore[`${shaderName}VertexShader`] = butterflyVertex; - - const butterflyMaterial = new ShaderMaterial(shaderName, scene, shaderName, { - attributes: ["position", "normal", "uv"], - uniforms: ["world", "worldView", "worldViewProjection", "view", "projection", "viewProjection", "time", "lightDirection", "playerPosition"], - defines: ["#define INSTANCES"], - samplers: ["butterflyTexture"] - }); - - butterflyMaterial.setTexture("butterflyTexture", new Texture(butterflyTexture, scene)); - butterflyMaterial.backFaceCulling = false; - - let elapsedSeconds = 0; - scene.onBeforeRenderObservable.add(() => { - elapsedSeconds += scene.getEngine().getDeltaTime() / 1000; - - if (scene.activeCamera === null) throw new Error("Active camera is null"); - - const starIndex = scene.lights.findIndex((light) => light instanceof PointLight); - if (starIndex === -1) throw new Error("Could not find star light"); - const star = scene.lights[starIndex] as PointLight; - - const lightDirection = star.position.subtract(scene.activeCamera.globalPosition).normalize(); - butterflyMaterial.setVector3("lightDirection", lightDirection); - - const playerPosition = player?.position ?? new Vector3(0, 0, 0); - butterflyMaterial.setVector3("playerPosition", playerPosition); - butterflyMaterial.setFloat("time", elapsedSeconds); - }); - - return butterflyMaterial; -} +import { Transformable } from "../../architecture/transformable"; + +export class ButterflyMaterial extends ShaderMaterial { + private elapsedSeconds = 0; + constructor(scene: Scene) { + const shaderName = "butterflyMaterial"; + Effect.ShadersStore[`${shaderName}FragmentShader`] = butterflyFragment; + Effect.ShadersStore[`${shaderName}VertexShader`] = butterflyVertex; + + super(shaderName, scene, shaderName, { + attributes: ["position", "normal", "uv"], + uniforms: ["world", "worldView", "worldViewProjection", "view", "projection", "viewProjection", "time", "lightDirection", "playerPosition"], + defines: ["#define INSTANCES"], + samplers: ["butterflyTexture"] + }); + + this.setVector3("lightDirection", new Vector3(0, 0, 0)); + this.setVector3("playerPosition", new Vector3(0, 0, 0)); + this.setFloat("time", 0); + this.setTexture("butterflyTexture", new Texture(butterflyTexture, scene)); + this.backFaceCulling = false; + } + + update(stars: Transformable[], playerPosition: Vector3, deltaSeconds: number) { + this.elapsedSeconds += deltaSeconds; + + const star = stars[0]; + const lightDirection = star.getTransform().getAbsolutePosition().subtract(playerPosition).normalize(); + this.setVector3("lightDirection", lightDirection); + + this.setVector3("playerPosition", playerPosition); + this.setFloat("time", this.elapsedSeconds); + } +} \ No newline at end of file diff --git a/src/ts/proceduralAssets/grass/grassBlade.ts b/src/ts/proceduralAssets/grass/grassBlade.ts index 7fb9e2c3c..0c7dc34c3 100644 --- a/src/ts/proceduralAssets/grass/grassBlade.ts +++ b/src/ts/proceduralAssets/grass/grassBlade.ts @@ -19,7 +19,6 @@ import { Mesh } from "@babylonjs/core/Meshes/mesh"; import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData"; import { Scene } from "@babylonjs/core/scene"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { createGrassMaterial } from "./grassMaterial"; // rotation using https://www.wikiwand.com/en/Rodrigues%27_rotation_formula function rotateAround(vector: Vector3, axis: Vector3, theta: number) { @@ -100,7 +99,5 @@ export function createGrassBlade(scene: Scene, nbStacks: number) { const grassBlade = new Mesh("grassBlade", scene); vertexData.applyToMesh(grassBlade); - grassBlade.material = createGrassMaterial(scene); - return grassBlade; } diff --git a/src/ts/proceduralAssets/grass/grassMaterial.ts b/src/ts/proceduralAssets/grass/grassMaterial.ts index fe9bcbc40..62c743583 100644 --- a/src/ts/proceduralAssets/grass/grassMaterial.ts +++ b/src/ts/proceduralAssets/grass/grassMaterial.ts @@ -21,49 +21,39 @@ import { Scene } from "@babylonjs/core/scene"; import grassFragment from "../../../shaders/grassMaterial/grassFragment.glsl"; import grassVertex from "../../../shaders/grassMaterial/grassVertex.glsl"; -import { TransformNode } from "@babylonjs/core/Meshes/transformNode"; import { Texture } from "@babylonjs/core/Materials/Textures/texture"; import perlinNoise from "../../../asset/perlin.png"; -import { PointLight } from "@babylonjs/core/Lights/pointLight"; - -export function createGrassMaterial(scene: Scene) { - const shaderName = "grassMaterial"; - Effect.ShadersStore[`${shaderName}FragmentShader`] = grassFragment; - Effect.ShadersStore[`${shaderName}VertexShader`] = grassVertex; - - const grassMaterial = new ShaderMaterial(shaderName, scene, shaderName, { - attributes: ["position", "normal"], - uniforms: ["world", "worldView", "worldViewProjection", "view", "projection", "viewProjection", "time", "lightDirection", "cameraPosition", "playerPosition"], - defines: ["#define INSTANCES"], - samplers: ["perlinNoise"] - }); - - const perlinTexture = new Texture(perlinNoise, scene); - - grassMaterial.backFaceCulling = false; - grassMaterial.setTexture("perlinNoise", perlinTexture); - - let elapsedSeconds = 0; - scene.onBeforeRenderObservable.add(() => { - elapsedSeconds += scene.getEngine().getDeltaTime() / 1000; - - if (scene.activeCamera === null) throw new Error("Active camera is null"); - - const starIndex = scene.lights.findIndex((light) => light instanceof PointLight); - if (starIndex === -1) throw new Error("Could not find star light"); - const star = scene.lights[starIndex] as PointLight; - - const lightDirection = star.position.subtract(scene.activeCamera.globalPosition).normalize(); - grassMaterial.setVector3("lightDirection", lightDirection); - - if (scene.activeCamera.parent !== null && !(scene.activeCamera.parent instanceof TransformNode)) throw new Error("Camera parent is not a TransformNode"); - - const playerPosition = scene.activeCamera.parent !== null ? scene.activeCamera.parent.getAbsolutePosition() : scene.activeCamera.globalPosition; // high y to avoid interaction with grass - const cameraPosition = scene.activeCamera.globalPosition; - grassMaterial.setVector3("playerPosition", playerPosition); - grassMaterial.setVector3("cameraPosition", cameraPosition); - grassMaterial.setFloat("time", elapsedSeconds); - }); - - return grassMaterial; +import { Transformable } from "../../architecture/transformable"; +import { Vector3 } from "@babylonjs/core/Maths/math.vector"; + +export class GrassMaterial extends ShaderMaterial { + private elapsedSeconds = 0; + constructor(scene: Scene) { + const shaderName = "grassMaterial"; + Effect.ShadersStore[`${shaderName}FragmentShader`] = grassFragment; + Effect.ShadersStore[`${shaderName}VertexShader`] = grassVertex; + + super(shaderName, scene, shaderName, { + attributes: ["position", "normal"], + uniforms: ["world", "worldView", "worldViewProjection", "view", "projection", "viewProjection", "time", "lightDirection", "cameraPosition", "playerPosition"], + defines: ["#define INSTANCES"], + samplers: ["perlinNoise"] + }); + + const perlinTexture = new Texture(perlinNoise, scene); + + this.backFaceCulling = false; + this.setTexture("perlinNoise", perlinTexture); + } + + update(stars: Transformable[], playerPosition: Vector3, deltaSeconds: number) { + this.elapsedSeconds += deltaSeconds; + + const star = stars[0]; + const lightDirection = star.getTransform().getAbsolutePosition().subtract(playerPosition).normalize(); + this.setVector3("lightDirection", lightDirection); + + this.setVector3("playerPosition", playerPosition); + this.setFloat("time", this.elapsedSeconds); + } } diff --git a/src/ts/randomizer.ts b/src/ts/randomizer.ts index aec492cd7..299c5421b 100644 --- a/src/ts/randomizer.ts +++ b/src/ts/randomizer.ts @@ -23,69 +23,30 @@ import { positionNearObjectBrightSide } from "./utils/positionNearObject"; import { CosmosJourneyer } from "./cosmosJourneyer"; import { BODY_TYPE } from "./model/common"; import { SystemSeed } from "./utils/systemSeed"; -import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { Quaternion } from "@babylonjs/core/Maths/math"; -import { setRotationQuaternion } from "./uberCore/transforms/basicTransform"; -const engine = new CosmosJourneyer(); +const engine = await CosmosJourneyer.CreateAsync(); -await engine.setup(); - -const starSystemView = engine.getStarSystemView(); +const starSystemView = engine.starSystemView; const scene = starSystemView.scene; -//check if url contains a seed -const urlParams = new URLSearchParams(window.location.search); -const urlStarMapX = urlParams.get("starMapX"); -const urlStarMapY = urlParams.get("starMapY"); -const urlStarMapZ = urlParams.get("starMapZ"); -const urlIndex = urlParams.get("index"); -const urlObjectIndex = urlParams.get("objectIndex"); -const urlPositionX = urlParams.get("positionX"); -const urlPositionY = urlParams.get("positionY"); -const urlPositionZ = urlParams.get("positionZ"); -const urlRotationX = urlParams.get("rotationQuaternionX"); -const urlRotationY = urlParams.get("rotationQuaternionY"); -const urlRotationZ = urlParams.get("rotationQuaternionZ"); -const urlRotationW = urlParams.get("rotationQuaternionW"); - -const starMapX = urlStarMapX !== null ? Number(urlStarMapX) : Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); -const starMapY = urlStarMapY !== null ? Number(urlStarMapY) : Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); -const starMapZ = urlStarMapZ !== null ? Number(urlStarMapZ) : Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); -const index = urlIndex !== null ? Number(urlIndex) : 0; -const objectIndex = urlObjectIndex !== null ? Number(urlObjectIndex) : 0; -const positionX = urlPositionX !== null ? Number(urlPositionX) : 0; -const positionY = urlPositionY !== null ? Number(urlPositionY) : 0; -const positionZ = urlPositionZ !== null ? Number(urlPositionZ) : 0; -const rotationX = urlRotationX !== null ? Number(urlRotationX) : 0; -const rotationY = urlRotationY !== null ? Number(urlRotationY) : 0; -const rotationZ = urlRotationZ !== null ? Number(urlRotationZ) : 0; -const rotationW = urlRotationW !== null ? Number(urlRotationW) : 1; +const starMapX = Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); +const starMapY = Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); +const starMapZ = Math.trunc((Math.random() * 2 - 1) * Number.MAX_SAFE_INTEGER * 0.1); +const index = 0; const seed = new SystemSeed(starMapX, starMapY, starMapZ, index); const starSystem = new StarSystemController(seed, scene); starSystemView.setStarSystem(starSystem, true); -engine.getStarMap().setCurrentStarSystem(seed); +engine.starMap.setCurrentStarSystem(seed); engine.init(true); -const position = new Vector3(positionX, positionY, positionZ); -const rotation = new Quaternion(rotationX, rotationY, rotationZ, rotationW); - const nbRadius = starSystem.model.getBodyTypeOfStar(0) === BODY_TYPE.BLACK_HOLE ? 8 : 3; -if (objectIndex >= starSystem.getOrbitalObjects().length) throw new Error(`Object index (${objectIndex}) out of bound (0 - ${starSystem.getOrbitalObjects().length - 1})!`); -const object = starSystem.getOrbitalObjects().length > 0 ? starSystem.getOrbitalObjects()[objectIndex] : starSystem.stellarObjects[0]; -if(position.length() === 0) positionNearObjectBrightSide(scene.getActiveController(), object, starSystem, nbRadius); -else { - const absolutePosition = Vector3.TransformCoordinates(position, object.getTransform().computeWorldMatrix()); - scene.getActiveController().getTransform().setAbsolutePosition(absolutePosition); - - const absoluteRotationQuaternion = rotation.multiply(object.getTransform().absoluteRotationQuaternion); - setRotationQuaternion(scene.getActiveController().getTransform(), absoluteRotationQuaternion); -} +const planet = starSystem.planets.length > 0 ? starSystem.planets[0] : starSystem.stellarObjects[0]; +positionNearObjectBrightSide(scene.getActiveController(), planet, starSystem, nbRadius); starSystemView.ui.setEnabled(true); starSystemView.showUI(); diff --git a/src/ts/saveFile/saveFileData.ts b/src/ts/saveFile/saveFileData.ts index ccc4e31d3..1071079a0 100644 --- a/src/ts/saveFile/saveFileData.ts +++ b/src/ts/saveFile/saveFileData.ts @@ -1,3 +1,5 @@ +import { isJsonStringValidUniverseCoordinates, UniverseCoordinates } from "./universeCoordinates"; + /** * Data structure for the save file to allow restoring current star system and position. */ @@ -8,54 +10,9 @@ export type SaveFileData = { version: string; /** - * The seed of the current star system. - */ - starSystem: { - starSectorX: number; - starSectorY: number; - starSectorZ: number; - starSectorIndex: number; - }; - - /** - * The index of the nearest orbital object. - */ - nearestOrbitalObjectIndex: number; - - /** - * The x coordinate of the player's position in the nearest orbital object's frame of reference. - */ - positionX: number; - - /** - * The y coordinate of the player's position in the nearest orbital object's frame of reference. - */ - positionY: number; - - /** - * The z coordinate of the player's position in the nearest orbital object's frame of reference. - */ - positionZ: number; - - /** - * The x component of the player's rotation quaternion in the nearest orbital object's frame of reference. - */ - rotationQuaternionX: number; - - /** - * The y component of the player's rotation quaternion in the nearest orbital object's frame of reference. - */ - rotationQuaternionY: number; - - /** - * The z component of the player's rotation quaternion in the nearest orbital object's frame of reference. - */ - rotationQuaternionZ: number; - - /** - * The w component of the player's rotation quaternion in the nearest orbital object's frame of reference. + * The coordinates of the current star system and the coordinates inside the star system. */ - rotationQuaternionW: number; + universeCoordinates: UniverseCoordinates; }; /** @@ -69,21 +26,9 @@ export function isJsonStringValidSaveFileData(jsonString: string): boolean { if (typeof data.version !== "string") return false; - if (typeof data.starSystem !== "object") return false; - if (typeof data.starSystem.starSectorX !== "number") return false; - if (typeof data.starSystem.starSectorY !== "number") return false; - if (typeof data.starSystem.starSectorZ !== "number") return false; - if (typeof data.starSystem.starSectorIndex !== "number") return false; + if (typeof data.universeCoordinates !== "object") return false; - if (typeof data.nearestOrbitalObjectIndex !== "number") return false; - - if (typeof data.positionX !== "number") return false; - if (typeof data.positionY !== "number") return false; - if (typeof data.positionZ !== "number") return false; - - if (typeof data.rotationQuaternionX !== "number") return false; - if (typeof data.rotationQuaternionY !== "number") return false; - if (typeof data.rotationQuaternionZ !== "number") return false; + if (!isJsonStringValidUniverseCoordinates(JSON.stringify(data.universeCoordinates))) return false; return true; } catch (e) { @@ -99,23 +44,5 @@ export function isJsonStringValidSaveFileData(jsonString: string): boolean { export function parseSaveFileData(jsonString: string): SaveFileData { if (!isJsonStringValidSaveFileData(jsonString)) throw new Error("Invalid save file data"); - const data = JSON.parse(jsonString); - - return { - version: data.version, - starSystem: { - starSectorX: data.starSystem.starSectorX, - starSectorY: data.starSystem.starSectorY, - starSectorZ: data.starSystem.starSectorZ, - starSectorIndex: data.starSystem.starSectorIndex - }, - nearestOrbitalObjectIndex: data.nearestOrbitalObjectIndex, - positionX: data.positionX, - positionY: data.positionY, - positionZ: data.positionZ, - rotationQuaternionX: data.rotationQuaternionX, - rotationQuaternionY: data.rotationQuaternionY, - rotationQuaternionZ: data.rotationQuaternionZ, - rotationQuaternionW: data.rotationQuaternionW - }; + return JSON.parse(jsonString); } diff --git a/src/ts/saveFile/universeCoordinates.ts b/src/ts/saveFile/universeCoordinates.ts new file mode 100644 index 000000000..5d4c2dd89 --- /dev/null +++ b/src/ts/saveFile/universeCoordinates.ts @@ -0,0 +1,80 @@ +import { SystemSeedSerialized } from "../utils/systemSeed"; + +export type UniverseCoordinates = { + /** + * The seed of the current star system. + */ + starSystem: SystemSeedSerialized; + + /** + * The index of the nearest orbital object. + */ + nearestOrbitalObjectIndex: number; + + /** + * The x coordinate of the player's position in the nearest orbital object's frame of reference. + */ + positionX: number; + + /** + * The y coordinate of the player's position in the nearest orbital object's frame of reference. + */ + positionY: number; + + /** + * The z coordinate of the player's position in the nearest orbital object's frame of reference. + */ + positionZ: number; + + /** + * The x component of the player's rotation quaternion in the nearest orbital object's frame of reference. + */ + rotationQuaternionX: number; + + /** + * The y component of the player's rotation quaternion in the nearest orbital object's frame of reference. + */ + rotationQuaternionY: number; + + /** + * The z component of the player's rotation quaternion in the nearest orbital object's frame of reference. + */ + rotationQuaternionZ: number; + + /** + * The w component of the player's rotation quaternion in the nearest orbital object's frame of reference. + */ + rotationQuaternionW: number; +}; + +/** + * Checks if a string is a valid universe coordinates json data. + * @param jsonString The string to check. + */ +export function isJsonStringValidUniverseCoordinates(jsonString: string): boolean { + try { + const data = JSON.parse(jsonString); + if (typeof data !== "object") return false; + + if (typeof data.starSystem !== "object") return false; + if (typeof data.starSystem.starSectorX !== "number") return false; + if (typeof data.starSystem.starSectorY !== "number") return false; + if (typeof data.starSystem.starSectorZ !== "number") return false; + if (typeof data.starSystem.index !== "number") return false; + + if (typeof data.nearestOrbitalObjectIndex !== "number") return false; + + if (typeof data.positionX !== "number") return false; + if (typeof data.positionY !== "number") return false; + if (typeof data.positionZ !== "number") return false; + + if (typeof data.rotationQuaternionX !== "number") return false; + if (typeof data.rotationQuaternionY !== "number") return false; + if (typeof data.rotationQuaternionZ !== "number") return false; + if (typeof data.rotationQuaternionW !== "number") return false; + + return true; + } catch (e) { + return false; + } +} \ No newline at end of file diff --git a/src/ts/spaceship/spaceship.ts b/src/ts/spaceship/spaceship.ts index c52489ff8..89d256e99 100644 --- a/src/ts/spaceship/spaceship.ts +++ b/src/ts/spaceship/spaceship.ts @@ -29,7 +29,13 @@ import { Observable } from "@babylonjs/core/Misc/observable"; import { Axis } from "@babylonjs/core/Maths/math.axis"; import { HavokPlugin } from "@babylonjs/core/Physics/v2/Plugins/havokPlugin"; import { setEnabledBody } from "../utils/havok"; -import { getForwardDirection, getUpwardDirection, rotate, translate } from "../uberCore/transforms/basicTransform"; +import { + getForwardDirection, + getUpwardDirection, + rotate, + setRotationQuaternion, + translate +} from "../uberCore/transforms/basicTransform"; import { TransformNode } from "@babylonjs/core/Meshes"; import { Assets } from "../assets"; import { PhysicsRaycastResult } from "@babylonjs/core/Physics/physicsRaycastResult"; @@ -37,6 +43,7 @@ import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2"; import { CollisionMask } from "../settings"; import { Transformable } from "../architecture/transformable"; import { WarpTunnel } from "../utils/warpTunnel"; +import { Quaternion } from "@babylonjs/core/Maths/math"; enum ShipState { FLYING, @@ -75,6 +82,7 @@ export class Spaceship implements Transformable { constructor(scene: Scene) { this.instanceRoot = Assets.CreateSpaceShipInstance(); + setRotationQuaternion(this.instanceRoot, Quaternion.Identity()); this.aggregate = new PhysicsAggregate( this.instanceRoot, @@ -224,7 +232,7 @@ export class Spaceship implements Transformable { this.warpDrive.update(currentForwardSpeed, this.closestObject.distance, this.closestObject.radius, deltaTime); // the warp throttle goes from 0.1 to 1 smoothly using an inverse function - if (this.warpDrive.isEnabled()) this.warpTunnel.setThrottle(1 - 1 / (1.1 * (1 + 1e-6 * this.warpDrive.getWarpSpeed()))); + if (this.warpDrive.isEnabled()) this.warpTunnel.setThrottle(1 - 1 / (1.1 * (1 + 1e-7 * this.warpDrive.getWarpSpeed()))); else this.warpTunnel.setThrottle(0); for (const thruster of this.mainThrusters) thruster.update(); diff --git a/src/ts/starSystem/StarSystemView.ts b/src/ts/starSystem/StarSystemView.ts index 226f17d14..ec041eb74 100644 --- a/src/ts/starSystem/StarSystemView.ts +++ b/src/ts/starSystem/StarSystemView.ts @@ -150,7 +150,7 @@ export class StarSystemView { this.scene.getEngine().loadingScreen.displayLoadingUI(); this.scene.getEngine().loadingScreen.loadingUIText = `Warping to ${this.getStarSystem().model.getName()}`; - this.getStarSystem().initPositions(100, this.chunkForge); + this.getStarSystem().initPositions(10, this.chunkForge); this.ui.createObjectOverlays(this.getStarSystem().getOrbitalObjects()); const firstBody = this.getStarSystem().getBodies()[0]; @@ -209,6 +209,9 @@ export class StarSystemView { update(deltaTime: number) { const starSystem = this.getStarSystem(); + Assets.ButterflyMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaTime); + Assets.GrassMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaTime); + this.chunkForge.update(); starSystem.update(deltaTime * Settings.TIME_MULTIPLIER, this.chunkForge); diff --git a/src/ts/uberCore/uberRenderingPipeline.ts b/src/ts/uberCore/uberRenderingPipeline.ts deleted file mode 100644 index c278f97c4..000000000 --- a/src/ts/uberCore/uberRenderingPipeline.ts +++ /dev/null @@ -1,43 +0,0 @@ -// This file is part of CosmosJourneyer -// -// Copyright (C) 2024 BarthĂ©lemy PalĂ©ologue -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see . - -import { Camera } from "@babylonjs/core/Cameras/camera"; -import { Engine } from "@babylonjs/core/Engines/engine"; -import { PostProcessRenderPipeline } from "@babylonjs/core/PostProcesses/RenderPipeline/postProcessRenderPipeline"; - -export class UberRenderingPipeline extends PostProcessRenderPipeline { - constructor(name: string, engine: Engine) { - super(engine, name); - } - - attachToCamera(camera: Camera) { - this._attachCameras([camera], false); - } - - detachCamera(camera: Camera) { - this._detachCameras([camera]); - } - - detachCameras() { - this._detachCameras(this.cameras); - } - - public override dispose() { - this.detachCameras(); - super.dispose(); - } -} diff --git a/src/ts/utils/base64.ts b/src/ts/utils/base64.ts new file mode 100644 index 000000000..361b789a5 --- /dev/null +++ b/src/ts/utils/base64.ts @@ -0,0 +1,7 @@ +export function encodeBase64(str: string): string { + return btoa(str); +} + +export function decodeBase64(str: string): string { + return atob(str); +} \ No newline at end of file diff --git a/src/ts/utils/systemSeed.ts b/src/ts/utils/systemSeed.ts index 947168749..bc422c151 100644 --- a/src/ts/utils/systemSeed.ts +++ b/src/ts/utils/systemSeed.ts @@ -20,6 +20,13 @@ import { hashVec3 } from "./hashVec3"; import { centeredRand } from "extended-random"; import { Settings } from "../settings"; +export type SystemSeedSerialized = { + starSectorX: number; + starSectorY: number; + starSectorZ: number; + index: number; +}; + export class SystemSeed { readonly starSectorX: number; readonly starSectorY: number; @@ -45,4 +52,17 @@ export class SystemSeed { toString(): string { return `${this.starSectorX},${this.starSectorY},${this.starSectorZ},${this.index}`; } + + serialize(): SystemSeedSerialized { + return { + starSectorX: this.starSectorX, + starSectorY: this.starSectorY, + starSectorZ: this.starSectorZ, + index: this.index + }; + } + + static Deserialize(data: SystemSeedSerialized): SystemSeed { + return new SystemSeed(data.starSectorX, data.starSectorY, data.starSectorZ, data.index); + } } diff --git a/src/ts/utils/warpTunnel.ts b/src/ts/utils/warpTunnel.ts index ab6c9d497..26da4fb92 100644 --- a/src/ts/utils/warpTunnel.ts +++ b/src/ts/utils/warpTunnel.ts @@ -27,6 +27,7 @@ import { Quaternion } from "@babylonjs/core/Maths/math"; import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial"; import { Color3, Color4 } from "@babylonjs/core/Maths/math.color"; import { getForwardDirection } from "../uberCore/transforms/basicTransform"; +import { uniformRandBool } from "extended-random"; /** * @see https://playground.babylonjs.com/#GLZ1PX#1241 (SPS) @@ -101,7 +102,7 @@ export class WarpTunnel implements Transformable { particle.position.addInPlace(direction.scale(Math.random() * 10)); particle.position.addInPlace(this.anchor.getAbsolutePosition()); - particle.velocity.copyFrom(direction.scale(200)); + particle.velocity.copyFrom(direction.scale(300)); particle.rotationQuaternion = rotationQuaternion; @@ -196,8 +197,10 @@ export class WarpTunnel implements Transformable { updateGlobals(); if (this.nbParticlesAlive < this.targetNbParticles && this.recycledParticles.length > 0) { - instanceFromStock(); - this.nbParticlesAlive++; + if(Math.random() < this.targetNbParticles / WarpTunnel.MAX_NB_PARTICLES) { + instanceFromStock(); + this.nbParticlesAlive++; + } } if (this.nbParticlesAlive === 0 && SPS.mesh.isEnabled()) {