diff --git a/.eslintrc.json b/.eslintrc.json index a760820a1..6cc7a9715 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -13,10 +13,59 @@ "plugins": ["@typescript-eslint"], "rules": { "import/no-cycle": "error", - "import/no-unresolved": "off", + "import/no-unresolved": "warn", "@typescript-eslint/switch-exhaustiveness-check": "error", - "@typescript-eslint/no-inferrable-types": "off", + "@typescript-eslint/no-inferrable-types": "warn", "eqeqeq": "error", - "camelcase": "warn" + "@typescript-eslint/naming-convention": [ + "error", + { + "selector": "enumMember", + "format": ["UPPER_CASE"] + }, + { + "selector": "memberLike", + "modifiers": ["public", "static"], + "format": ["PascalCase", "UPPER_CASE"], + "leadingUnderscore": "forbid" + }, + { + "selector": "memberLike", + "modifiers": ["private", "static"], + "format": ["PascalCase", "UPPER_CASE"], + "leadingUnderscore": "forbid" + }, + { + "selector": "typeLike", + "format": ["PascalCase"] + }, + { + "selector": "variable", + "modifiers": ["exported", "const", "global"], + "format": ["PascalCase"], + "leadingUnderscore": "forbid" + }, + { + "selector": "function", + "format": ["camelCase", "snake_case"], + "leadingUnderscore": "forbid" + }, + { + "selector": "function", + "modifiers": ["exported", "global"], + "format": ["camelCase"], + "leadingUnderscore": "forbid" + }, + { + "selector": "interface", + "format": ["PascalCase"], + "leadingUnderscore": "forbid" + }, + { + "selector": "class", + "format": ["PascalCase"], + "leadingUnderscore": "forbid" + } + ] } } diff --git a/package.json b/package.json index 07fbfe420..624c92b78 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, - "version": "1.6.4", + "version": "1.7.0", "description": "CosmosJourneyer", "name": "cosmos-journeyer", "scripts": { diff --git a/src/asset/sound/318688__limitsnap_creations__rocket-thrust-effect.mp3 b/src/asset/sound/318688__limitsnap_creations__rocket-thrust-effect.mp3 new file mode 100644 index 000000000..dfed2fecb Binary files /dev/null and b/src/asset/sound/318688__limitsnap_creations__rocket-thrust-effect.mp3 differ diff --git a/src/asset/spaceship/wanderer.glb b/src/asset/spaceship/wanderer.glb new file mode 100644 index 000000000..203681483 Binary files /dev/null and b/src/asset/spaceship/wanderer.glb differ diff --git a/src/html/mainMenu.html b/src/html/mainMenu.html index 709535d2b..263150fde 100644 --- a/src/html/mainMenu.html +++ b/src/html/mainMenu.html @@ -1,7 +1,7 @@
diff --git a/src/shaders/starfieldFragment.glsl b/src/shaders/starfieldFragment.glsl index d62f5c60e..02073300b 100644 --- a/src/shaders/starfieldFragment.glsl +++ b/src/shaders/starfieldFragment.glsl @@ -48,7 +48,7 @@ void main() { vec2 starfieldUV = vec2(0.0); // if the pixel is at the far plane - if (depth == 1.0) { + if (screenColor == vec4(0.0) && depth == 1.0) { // get the starfield color // get spherical coordinates uv for the starfield texture starfieldUV = vec2( diff --git a/src/styles/mainMenu.scss b/src/styles/mainMenu.scss index 592c3a2ef..4d9fe2913 100644 --- a/src/styles/mainMenu.scss +++ b/src/styles/mainMenu.scss @@ -39,6 +39,16 @@ font-size: 1.5em; padding: 10px; transition: 0.2s ease-in-out; + + a { + color: white; + transition: 0.1s; + text-decoration: underline solid rgba(255, 255, 255, 0.5); + + &:hover { + text-decoration: underline solid white; + } + } } .rightPanel { diff --git a/src/ts/assets.ts b/src/ts/assets.ts index e4701bf70..ff6ffb375 100644 --- a/src/ts/assets.ts +++ b/src/ts/assets.ts @@ -46,6 +46,7 @@ import spaceship from "../asset/spaceship/spaceship2.glb"; import shipCarrier from "../asset/spacestation/shipcarrier.glb"; import banana from "../asset/banana/banana.glb"; import endeavorSpaceship from "../asset/spaceship/endeavour.glb"; +import wanderer from "../asset/spaceship/wanderer.glb"; import character from "../asset/character/character.glb"; import rock from "../asset/rock.glb"; import landingPad from "../asset/landingpad.glb"; @@ -65,6 +66,8 @@ import disableWarpDriveSound from "../asset/sound/204418__nhumphrey__large-engin import acceleratingWarpDriveSound from "../asset/sound/539503__timbre__endless-acceleration.mp3"; import deceleratingWarpDriveSound from "../asset/sound/539503__timbre_endless-deceleration.mp3"; +import thrusterSound from "../asset/sound/318688__limitsnap_creations__rocket-thrust-effect.mp3"; + import starMapBackgroundMusic from "../asset/sound/455855__andrewkn__wandering.mp3"; import { Texture } from "@babylonjs/core/Materials/Textures/texture"; @@ -84,7 +87,6 @@ import { createButterfly } from "./proceduralAssets/butterfly/butterfly"; import { createGrassBlade } from "./proceduralAssets/grass/grassBlade"; import { ButterflyMaterial } from "./proceduralAssets/butterfly/butterflyMaterial"; import { GrassMaterial } from "./proceduralAssets/grass/grassMaterial"; -import { Observable } from "@babylonjs/core/Misc/observable"; export class Assets { static IS_READY = false; @@ -115,6 +117,7 @@ export class Assets { private static SPACESHIP: Mesh; private static ENDEAVOR_SPACESHIP: Mesh; + private static WANDERER: Mesh; private static SPACE_STATION: Mesh; private static BANANA: Mesh; private static CHARACTER: Mesh; @@ -149,6 +152,8 @@ export class Assets { public static ACCELERATING_WARP_DRIVE_SOUND: Sound; public static DECELERATING_WARP_DRIVE_SOUND: Sound; + public static THRUSTER_SOUND: Sound; + public static STAR_MAP_BACKGROUND_MUSIC: Sound; public static MAIN_MENU_BACKGROUND_MUSIC: Sound; @@ -205,6 +210,17 @@ export class Assets { console.log("Endeavor Spaceship loaded"); }; + const wandererTask = Assets.MANAGER.addMeshTask("wandererTask", "", "", wanderer); + wandererTask.onSuccess = function (task: MeshAssetTask) { + Assets.WANDERER = task.loadedMeshes[0] as Mesh; + + for (const mesh of Assets.WANDERER.getChildMeshes()) { + mesh.isVisible = false; + } + + console.log("Wanderer loaded"); + }; + const spacestationTask = Assets.MANAGER.addMeshTask("spacestationTask", "", "", shipCarrier); spacestationTask.onSuccess = function (task: MeshAssetTask) { Assets.SPACE_STATION = task.loadedMeshes[0] as Mesh; @@ -401,6 +417,18 @@ export class Assets { console.log("Decelerating warp drive sound loaded"); }; + const thrusterSoundTask = Assets.MANAGER.addBinaryFileTask("thrusterSoundTask", thrusterSound); + thrusterSoundTask.onSuccess = function (task) { + Assets.THRUSTER_SOUND = new Sound("ThrusterSound", task.data, scene); + Assets.THRUSTER_SOUND.updateOptions({ + playbackRate: 1.0, + volume: 0.5, + loop: true + }); + + console.log("Thruster sound loaded"); + }; + const starMapBackgroundMusicTask = Assets.MANAGER.addBinaryFileTask("starMapBackgroundMusicTask", starMapBackgroundMusic); starMapBackgroundMusicTask.onSuccess = function (task) { Assets.STAR_MAP_BACKGROUND_MUSIC = new Sound("StarMapBackgroundMusic", task.data, scene, null, { @@ -454,6 +482,10 @@ export class Assets { return instance; } + static CreateWandererInstance(): InstancedMesh { + return Assets.WANDERER.instantiateHierarchy(null, { doNotInstantiate: false }) as InstancedMesh; + } + static CreateSpaceStationInstance(): InstancedMesh { return Assets.SPACE_STATION.instantiateHierarchy(null, { doNotInstantiate: false }) as InstancedMesh; } diff --git a/src/ts/audio/audioManager.ts b/src/ts/audio/audioManager.ts new file mode 100644 index 000000000..5fa6438f7 --- /dev/null +++ b/src/ts/audio/audioManager.ts @@ -0,0 +1,24 @@ +import { AudioInstance } from "../utils/audioInstance"; + + +export class AudioManager { + private static ENABLED_MASK = 0b1111; + + private static readonly SOUNDS: AudioInstance[] = []; + + public static RegisterSound(sound: AudioInstance) { + this.SOUNDS.push(sound); + } + + public static SetMask(mask: number) { + this.ENABLED_MASK = mask; + } + + public static Update(deltaSeconds: number) { + this.SOUNDS.forEach(sound => { + const isSoundEnabled = (sound.mask & this.ENABLED_MASK) !== 0; + sound.setMaskFactor(isSoundEnabled ? 1 : 0); + sound.update(deltaSeconds); + }); + } +} \ No newline at end of file diff --git a/src/ts/audio/audioMasks.ts b/src/ts/audio/audioMasks.ts new file mode 100644 index 000000000..31a651199 --- /dev/null +++ b/src/ts/audio/audioMasks.ts @@ -0,0 +1,4 @@ +export const AudioMasks = { + STAR_SYSTEM_VIEW: 0b0001, + STAR_MAP_VIEW: 0b0010, +} \ No newline at end of file diff --git a/src/ts/cosmosJourneyer.ts b/src/ts/cosmosJourneyer.ts index 5584f9f1f..6b6f45fe3 100644 --- a/src/ts/cosmosJourneyer.ts +++ b/src/ts/cosmosJourneyer.ts @@ -23,7 +23,6 @@ import { Tools } from "@babylonjs/core/Misc/tools"; import { VideoRecorder } from "@babylonjs/core/Misc/videoRecorder"; import "@babylonjs/core/Misc/screenshotTools"; import { StarMap } from "./starmap/starMap"; -import { Scene } from "@babylonjs/core/scene"; import "@babylonjs/core/Physics/physicsEngineComponent"; import HavokPhysics from "@babylonjs/havok"; @@ -44,6 +43,8 @@ import { UniverseCoordinates } from "./saveFile/universeCoordinates"; import { View } from "./utils/view"; import { updateInputDevices } from "./inputs/devices"; import { Assets } from "./assets"; +import { AudioManager } from "./audio/audioManager"; +import { AudioMasks } from "./audio/audioMasks"; enum EngineState { UNINITIALIZED, @@ -84,7 +85,7 @@ export class CosmosJourneyer { const activeControls = this.starSystemView.scene.getActiveController(); if (activeControls instanceof ShipControls) { activeControls.spaceship.enableWarpDrive(); - activeControls.thirdPersonCamera.radius = 30; + activeControls.thirdPersonCamera.radius = ShipControls.BASE_CAMERA_RADIUS; } }); @@ -92,6 +93,7 @@ export class CosmosJourneyer { this.starMap.detachControl(); this.starSystemView.attachControl(); this.activeView = this.starSystemView; + AudioManager.SetMask(AudioMasks.STAR_SYSTEM_VIEW); this.mainMenu = new MainMenu(starSystemView); this.mainMenu.onStartObservable.add(() => { @@ -100,6 +102,7 @@ export class CosmosJourneyer { this.starSystemView.getSpaceshipControls().spaceship.enableWarpDrive(); this.starSystemView.showUI(); this.starSystemView.ui.setEnabled(true); + this.starSystemView.ui.setTarget(this.starSystemView.getStarSystem().getClosestToScreenCenterOrbitalObject()); }); this.mainMenu.onLoadSaveObservable.add((saveData: SaveFileData) => { @@ -178,14 +181,14 @@ export class CosmosJourneyer { const havokInstance = await HavokPhysics(); console.log(`Havok initialized`); - // Init starmap view - const starMap = new StarMap(engine); - // Init star system view const starSystemView = new StarSystemView(engine, havokInstance); await starSystemView.initAssets(); + // Init starmap view + const starMap = new StarMap(engine); + return new CosmosJourneyer(engine, starSystemView, starMap); } @@ -218,6 +221,7 @@ export class CosmosJourneyer { this.engine.runRenderLoop(() => { updateInputDevices(); + AudioManager.Update(this.engine.getDeltaTime() / 1000); if (this.isPaused()) return; this.activeView.render(); @@ -231,8 +235,7 @@ export class CosmosJourneyer { public toggleStarMap(): void { if (this.activeView === this.starSystemView) { this.starSystemView.unZoom(() => { - this.starSystemView.stopBackgroundSounds(); - this.starMap.startBackgroundMusic(); + AudioManager.SetMask(AudioMasks.STAR_MAP_VIEW); this.activeView.detachControl(); this.starMap.attachControl(); @@ -242,9 +245,10 @@ export class CosmosJourneyer { starMap.focusOnCurrentSystem(); }); } else { - this.starMap.stopBackgroundMusic(); this.activeView.detachControl(); this.starSystemView.attachControl(); + + AudioManager.SetMask(AudioMasks.STAR_SYSTEM_VIEW); this.activeView = this.starSystemView; this.starSystemView.showUI(); } diff --git a/src/ts/mainMenu/mainMenu.ts b/src/ts/mainMenu/mainMenu.ts index e594ad700..17ebc4417 100644 --- a/src/ts/mainMenu/mainMenu.ts +++ b/src/ts/mainMenu/mainMenu.ts @@ -131,7 +131,10 @@ Math.trunc((Math.random() * 2 - 1) * 1000), const version = document.getElementById("version"); if (version === null) throw new Error("#version does not exist!"); - version.textContent = `Alpha ${packageInfo.version}`; + // children a elements has the version number as textContent + const childLink = version.querySelector("a"); + if (childLink === null) throw new Error("version link does not exist!"); + childLink.textContent = `Alpha ${packageInfo.version}`; this.version = version; document.querySelectorAll("#menuItems li").forEach((li) => { diff --git a/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts b/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts index e747f6dc1..64e9e67ba 100644 --- a/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts +++ b/src/ts/planets/telluricPlanet/terrain/chunks/chunkTree.ts @@ -36,7 +36,7 @@ import { ChunkForge } from "./chunkForge"; /** * A quadTree is defined recursively */ -type quadTree = quadTree[] | PlanetChunk; +type QuadTree = QuadTree[] | PlanetChunk; /** * A ChunkTree is a structure designed to manage LOD using a quadtree @@ -45,7 +45,7 @@ export class ChunkTree { readonly minDepth: number; // minimum depth of the tree readonly maxDepth: number; // maximum depth of the tree - private tree: quadTree = []; + private tree: QuadTree = []; private readonly rootChunkLength: number; @@ -105,7 +105,7 @@ export class ChunkTree { * @param tree the tree to explore * @param f the function to apply on every chunk */ - public executeOnEveryChunk(f: (chunk: PlanetChunk) => void, tree: quadTree = this.tree): void { + public executeOnEveryChunk(f: (chunk: PlanetChunk) => void, tree: QuadTree = this.tree): void { if (tree instanceof PlanetChunk) f(tree); else for (const stem of tree) this.executeOnEveryChunk(f, stem); } @@ -115,12 +115,12 @@ export class ChunkTree { * @param tree The tree to delete * @param newChunks */ - private requestDeletion(tree: quadTree, newChunks: PlanetChunk[]): void { + private requestDeletion(tree: QuadTree, newChunks: PlanetChunk[]): void { const chunksToDelete = this.getChunkList(tree); this.deleteSemaphores.push(new DeleteSemaphore(newChunks, chunksToDelete)); } - public getChunkList(tree: quadTree): PlanetChunk[] { + public getChunkList(tree: QuadTree): PlanetChunk[] { const result: PlanetChunk[] = []; this.executeOnEveryChunk((chunk) => result.push(chunk), tree); return result; @@ -140,20 +140,20 @@ export class ChunkTree { this.tree = this.updateLODRecursively(observerPosition, chunkForge); } - private getAverageHeight(tree: quadTree): number { + private getAverageHeight(tree: QuadTree): number { if (tree instanceof PlanetChunk) return tree.getAverageHeight(); else if (tree.length > 0) return 0.25 * (this.getAverageHeight(tree[0]) + this.getAverageHeight(tree[1]) + this.getAverageHeight(tree[2]) + this.getAverageHeight(tree[3])); else return 0; } - private getMinAverageHeight(tree: quadTree): number { + private getMinAverageHeight(tree: QuadTree): number { if (tree instanceof PlanetChunk) return tree.getAverageHeight(); else if (tree.length > 0) return Math.min(this.getMinAverageHeight(tree[0]), this.getMinAverageHeight(tree[1]), this.getMinAverageHeight(tree[2]), this.getMinAverageHeight(tree[3])); else return 0; } - private getMaxAverageHeight(tree: quadTree): number { + private getMaxAverageHeight(tree: QuadTree): number { if (tree instanceof PlanetChunk) return tree.getAverageHeight(); else if (tree.length > 0) return Math.max(this.getMaxAverageHeight(tree[0]), this.getMaxAverageHeight(tree[1]), this.getMaxAverageHeight(tree[2]), this.getMaxAverageHeight(tree[3])); @@ -168,7 +168,7 @@ export class ChunkTree { * @param walked The position of the current root relative to the absolute root * @returns The updated tree */ - private updateLODRecursively(observerPositionW: Vector3, chunkForge: ChunkForge, tree: quadTree = this.tree, walked: number[] = []): quadTree { + private updateLODRecursively(observerPositionW: Vector3, chunkForge: ChunkForge, tree: QuadTree = this.tree, walked: number[] = []): QuadTree { if (walked.length === this.maxDepth) return tree; const nodeRelativePosition = getChunkSphereSpacePositionFromPath(walked, this.direction, this.rootChunkLength / 2, getRotationQuaternion(this.parent)); diff --git a/src/ts/spaceship/abstractThruster.ts b/src/ts/spaceship/abstractThruster.ts index 98c4299ef..7cd3839c6 100644 --- a/src/ts/spaceship/abstractThruster.ts +++ b/src/ts/spaceship/abstractThruster.ts @@ -19,10 +19,8 @@ import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial"; import { Vector3 } from "@babylonjs/core/Maths/math"; import { Color3 } from "@babylonjs/core/Maths/math.color"; import { AbstractMesh, MeshBuilder } from "@babylonjs/core/Meshes"; -import { DirectionnalParticleSystem } from "../utils/particleSystem"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; -import { getDownwardDirection } from "../uberCore/transforms/basicTransform"; -import { LocalDirection } from "../uberCore/localDirections"; +import { SolidPlume } from "../utils/solidPlume"; export abstract class AbstractThruster { readonly mesh: AbstractMesh; @@ -33,21 +31,15 @@ export abstract class AbstractThruster { readonly localNozzleDown: Vector3; - readonly plume: DirectionnalParticleSystem; + readonly plume: SolidPlume; readonly parentAggregate: PhysicsAggregate; - readonly leverage: number; - - protected abstract maxAuthority: number; - - constructor(mesh: AbstractMesh, direction: Vector3, parentAggregate: PhysicsAggregate) { + protected constructor(mesh: AbstractMesh, direction: Vector3, parentAggregate: PhysicsAggregate) { this.mesh = mesh; - this.leverage = this.mesh.position.length(); - this.localNozzleDown = direction; - this.plume = new DirectionnalParticleSystem(mesh, this.localNozzleDown); + this.plume = new SolidPlume(mesh, mesh.getScene()); this.parentAggregate = parentAggregate; @@ -66,46 +58,10 @@ export abstract class AbstractThruster { return this.throttle; } - public setMaxAuthority(maxAuthority: number): void { - this.maxAuthority = maxAuthority; - } - - public getMaxAuthority(): number { - return this.maxAuthority; - } - - /** - * Returns the theoretical authority of the thruster in the given direction between 0 and 1 (independent of throttle) - * @param direction The direction (in local space) - * @returns - */ - public getAuthority01(direction: Vector3): number { - return Math.max(0, Vector3.Dot(this.localNozzleDown.negate(), direction)); - } - - public getAuthorityAroundAxisNormalized(rotationAxis: Vector3): number { - const thrusterPosition = this.mesh.position; - const thrusterPositionOnAxis = rotationAxis.scale(Vector3.Dot(thrusterPosition, rotationAxis)); + public update(deltaSeconds: number): void { + this.plume.update(deltaSeconds); - const thrusterPositionToAxisNormalized = thrusterPosition.subtract(thrusterPositionOnAxis).normalize(); - - const thrusterRotationAxis = Vector3.Cross(this.localNozzleDown.negate(), thrusterPositionToAxisNormalized); - return Vector3.Dot(thrusterRotationAxis, rotationAxis); - } - - public getRollAuthorityNormalized(): number { - return this.getAuthorityAroundAxisNormalized(LocalDirection.FORWARD); - } - - public getPitchAuthorityNormalized(): number { - return this.getAuthorityAroundAxisNormalized(LocalDirection.RIGHT); - } - - public update(): void { - this.plume.emitRate = this.throttle * 1000; - this.plume.setDirection(getDownwardDirection(this.parentAggregate.transformNode)); - //const parentAcceleration = this.parentAggregate.body.getL - //this.plume.applyAcceleration(this.parent.acceleration.negate()); + this.plume.setThrottle(this.throttle); if (this.throttle > 0) { this.helperMesh.scaling = new Vector3(0.8, 0.8, 0.8); @@ -113,6 +69,4 @@ export abstract class AbstractThruster { this.helperMesh.scaling = new Vector3(0.5, 0.5, 0.5); } } - - public abstract applyForce(): void; } diff --git a/src/ts/spaceship/mainThruster.ts b/src/ts/spaceship/mainThruster.ts index abd119f24..bec1fa7a0 100644 --- a/src/ts/spaceship/mainThruster.ts +++ b/src/ts/spaceship/mainThruster.ts @@ -19,7 +19,6 @@ import { Vector3 } from "@babylonjs/core/Maths/math"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; import { AbstractThruster } from "./abstractThruster"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; -import { getDownwardDirection } from "../uberCore/transforms/basicTransform"; export class MainThruster extends AbstractThruster { protected readonly maxAuthority = 3e3; @@ -35,12 +34,4 @@ export class MainThruster extends AbstractThruster { public updateThrottle(delta: number): void { this.throttle = Math.max(Math.min(1, this.throttle + delta), 0); } - - public applyForce(): void { - const thrustDirection = getDownwardDirection(this.mesh); - const force = thrustDirection.scale(this.maxAuthority * this.throttle); - - // make the ship spin (apply force at the position of the thruster then apply the same force at the center of mass in the opposite direction) - this.parentAggregate.body.applyForce(force, this.parentAggregate.body.getObjectCenterWorld()); - } } diff --git a/src/ts/spaceship/rcsThruster.ts b/src/ts/spaceship/rcsThruster.ts deleted file mode 100644 index cdccc6699..000000000 --- a/src/ts/spaceship/rcsThruster.ts +++ /dev/null @@ -1,57 +0,0 @@ -// This file is part of Cosmos Journeyer -// -// 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 { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; -import { AbstractThruster } from "./abstractThruster"; -import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; -import { getDownwardDirection } from "../uberCore/transforms/basicTransform"; - -export class RCSThruster extends AbstractThruster { - protected override maxAuthority = 30; - - constructor(mesh: AbstractMesh, direction: Vector3, parentAggregate: PhysicsAggregate) { - super(mesh, direction, parentAggregate); - - this.plume.maxSize = 0.3; - this.plume.minSize = 0.3; - - this.plume.minLifeTime = 0.2; - this.plume.maxLifeTime = 0.2; - } - - public activate(): void { - this.throttle = 1; - } - - public setThrottle(throttle: number): void { - if (throttle < 0 || throttle > 1) throw new Error("Throttle must be between 0 and 1"); - this.throttle = throttle; - } - - public deactivate(): void { - this.throttle = 0; - } - - public applyForce(): void { - // the nozzle is directed upward - const thrustDirection = getDownwardDirection(this.mesh); - const force = thrustDirection.scale(this.maxAuthority * this.throttle); - - this.parentAggregate.body.applyForce(force, this.mesh.getAbsolutePosition()); - } -} diff --git a/src/ts/spaceship/shipControls.ts b/src/ts/spaceship/shipControls.ts index 1d5e87c25..7c804b0ad 100644 --- a/src/ts/spaceship/shipControls.ts +++ b/src/ts/spaceship/shipControls.ts @@ -17,7 +17,6 @@ import { Scene } from "@babylonjs/core/scene"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { LocalDirection } from "../uberCore/localDirections"; import { getUpwardDirection, pitch, roll } from "../uberCore/transforms/basicTransform"; import { TransformNode } from "@babylonjs/core/Meshes"; import { Controls } from "../uberCore/controls"; @@ -38,6 +37,8 @@ export class ShipControls implements Controls { private isCameraShaking = false; + static BASE_CAMERA_RADIUS = 60; + private baseFov: number; private targetFov: number; @@ -46,9 +47,9 @@ export class ShipControls implements Controls { this.firstPersonCamera = new FreeCamera("firstPersonCamera", Vector3.Zero(), scene); this.firstPersonCamera.parent = this.getTransform(); - this.firstPersonCamera.position = new Vector3(0, 1, 0); + this.firstPersonCamera.position = new Vector3(0, 1.2, 3.5); - this.thirdPersonCamera = new ArcRotateCamera("thirdPersonCamera", -3.14 / 2, 3.14 / 2, 30, Vector3.Zero(), scene); + this.thirdPersonCamera = new ArcRotateCamera("thirdPersonCamera", -3.14 / 2, 3.14 / 2.2, ShipControls.BASE_CAMERA_RADIUS, Vector3.Zero(), scene); this.thirdPersonCamera.parent = this.getTransform(); this.thirdPersonCamera.lowerRadiusLimit = 10; this.thirdPersonCamera.upperRadiusLimit = 500; @@ -63,6 +64,17 @@ export class ShipControls implements Controls { this.spaceship.toggleWarpDrive(); }); + SpaceShipControlsInputs.map.landing.on("complete", () => { + if (this.spaceship.getClosestWalkableObject() !== null) { + this.spaceship.engageLanding(null); + } + }); + + SpaceShipControlsInputs.map.throttleToZero.on("complete", () => { + this.spaceship.setMainEngineThrottle(0); + this.spaceship.getWarpDrive().increaseTargetThrottle(-this.spaceship.getWarpDrive().getTargetThrottle()); + }); + this.baseFov = this.thirdPersonCamera.fov; this.targetFov = this.baseFov; @@ -97,51 +109,25 @@ export class ShipControls implements Controls { this.spaceship.update(deltaTime); let [inputRoll, inputPitch] = SpaceShipControlsInputs.map.rollPitch.value; - if(SpaceShipControlsInputs.map.ignorePointer.value > 0) { + if (SpaceShipControlsInputs.map.ignorePointer.value > 0) { inputRoll *= 0; inputPitch *= 0; } if (this.spaceship.getWarpDrive().isDisabled()) { - for (const thruster of this.spaceship.mainThrusters) { - thruster.updateThrottle(2 * deltaTime * SpaceShipControlsInputs.map.throttle.value * thruster.getAuthority01(LocalDirection.FORWARD)); - thruster.updateThrottle(2 * deltaTime * -SpaceShipControlsInputs.map.throttle.value * thruster.getAuthority01(LocalDirection.BACKWARD)); - - thruster.updateThrottle(2 * deltaTime * SpaceShipControlsInputs.map.upDown.value * thruster.getAuthority01(LocalDirection.UP)); - thruster.updateThrottle(2 * deltaTime * -SpaceShipControlsInputs.map.upDown.value * thruster.getAuthority01(LocalDirection.DOWN)); - - /*thruster.updateThrottle(2 * deltaTime * input.getXAxis() * thruster.getAuthority01(LocalDirection.LEFT)); - thruster.updateThrottle(2 * deltaTime * -input.getXAxis() * thruster.getAuthority01(LocalDirection.RIGHT));*/ - } + this.spaceship.increaseMainEngineThrottle(deltaTime * SpaceShipControlsInputs.map.throttle.value); this.spaceship.aggregate.body.applyForce( - getUpwardDirection(this.getTransform()).scale(9.8 * 10 * SpaceShipControlsInputs.map.upDown.value), - this.spaceship.aggregate.body.getObjectCenterWorld() + getUpwardDirection(this.getTransform()).scale(9.8 * 10 * SpaceShipControlsInputs.map.upDown.value), + this.spaceship.aggregate.body.getObjectCenterWorld() ); - - if (SpaceShipControlsInputs.map.landing.state === "complete") { - if (this.spaceship.getClosestWalkableObject() !== null) { - this.spaceship.engageLanding(null); - } - } - - for (const rcsThruster of this.spaceship.rcsThrusters) { - let throttle = 0; - - // rcs rotation contribution - if (inputRoll < 0 && rcsThruster.getRollAuthorityNormalized() > 0.2) throttle = Math.max(throttle, Math.abs(inputRoll)); - else if (inputRoll > 0 && rcsThruster.getRollAuthorityNormalized() < -0.2) throttle = Math.max(throttle, Math.abs(inputRoll)); - - if (inputPitch < 0 && rcsThruster.getPitchAuthorityNormalized() > 0.2) throttle = Math.max(throttle, Math.abs(inputPitch)); - else if (inputPitch > 0 && rcsThruster.getPitchAuthorityNormalized() < -0.2) throttle = Math.max(throttle, Math.abs(inputPitch)); - - rcsThruster.setThrottle(throttle); - } } else { + this.spaceship.getWarpDrive().increaseTargetThrottle(deltaTime * SpaceShipControlsInputs.map.throttle.value); + } + + if (!this.spaceship.isLanded()) { roll(this.getTransform(), inputRoll * deltaTime); pitch(this.getTransform(), inputPitch * deltaTime); - - this.spaceship.getWarpDrive().increaseTargetThrottle(deltaTime * SpaceShipControlsInputs.map.throttle.value); } // camera shake diff --git a/src/ts/spaceship/spaceShipControlsInputs.ts b/src/ts/spaceship/spaceShipControlsInputs.ts index bc70d2e04..e5a49875e 100644 --- a/src/ts/spaceship/spaceShipControlsInputs.ts +++ b/src/ts/spaceship/spaceShipControlsInputs.ts @@ -10,7 +10,7 @@ const keyboard = InputDevices.KEYBOARD; const pointer = InputDevices.POINTER; const landingAction = new Action({ - bindings: [keyboard.getControl("Space"), gamepad.getControl("A")] + bindings: [keyboard.getControl("KeyL"), gamepad.getControl("A")] }); const landingInteraction = new PressInteraction(landingAction); @@ -85,6 +85,12 @@ const ignorePointer = new Action({ bindings: [keyboard.getControl("AltLeft")] }); +const throttleToZero = new Action({ + bindings: [keyboard.getControl("KeyX")] +}); + +const throttleToZeroInteraction = new PressInteraction(throttleToZero); + export const SpaceShipControlsInputs = new InputMap<{ landing: PressInteraction, upDown: Action, @@ -92,7 +98,8 @@ export const SpaceShipControlsInputs = new InputMap<{ rollPitch: Action<[number, number]>, toggleFlightAssist: PressInteraction, toggleWarpDrive: PressInteraction, - ignorePointer: Action + ignorePointer: Action, + throttleToZero: PressInteraction }>("SpaceShipInputs", { landing: landingInteraction, upDown: upDownAction, @@ -100,7 +107,8 @@ export const SpaceShipControlsInputs = new InputMap<{ rollPitch: rollPitch, toggleFlightAssist: toggleFlightAssistInteraction, toggleWarpDrive: toggleWarpDriveInteraction, - ignorePointer: ignorePointer + ignorePointer: ignorePointer, + throttleToZero: throttleToZeroInteraction }); InputMaps.push(SpaceShipControlsInputs); \ No newline at end of file diff --git a/src/ts/spaceship/spaceship.ts b/src/ts/spaceship/spaceship.ts index 211cfbd05..d857fd2b4 100644 --- a/src/ts/spaceship/spaceship.ts +++ b/src/ts/spaceship/spaceship.ts @@ -18,9 +18,7 @@ import { Scene } from "@babylonjs/core/scene"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; -import { MainThruster } from "./mainThruster"; import { WarpDrive } from "./warpDrive"; -import { RCSThruster } from "./rcsThruster"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; import { IPhysicsCollisionEvent, PhysicsMotionType, PhysicsShapeType } from "@babylonjs/core/Physics/v2/IPhysicsEnginePlugin"; import { PhysicsShapeMesh } from "@babylonjs/core/Physics/v2/physicsShape"; @@ -41,6 +39,9 @@ import { LandingPad } from "../landingPad/landingPad"; import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2"; import { HyperSpaceTunnel } from "../utils/hyperSpaceTunnel"; import { AudioInstance } from "../utils/audioInstance"; +import { AudioManager } from "../audio/audioManager"; +import { MainThruster } from "./mainThruster"; +import { AudioMasks } from "../audio/audioMasks"; enum ShipState { FLYING, @@ -56,11 +57,11 @@ export class Spaceship implements Transformable { private flightAssistEnabled = true; - readonly mainThrusters: MainThruster[] = []; - readonly rcsThrusters: RCSThruster[] = []; - private readonly warpDrive = new WarpDrive(false); + private mainEngineThrottle = 0; + private mainEngineTargetSpeed = 0; + private closestWalkableObject: Transformable | null = null; private landingTarget: Transformable | null = null; @@ -80,16 +81,19 @@ export class Spaceship implements Transformable { private targetLandingPad: LandingPad | null = null; + private mainThrusters: MainThruster[] = []; + readonly enableWarpDriveSound: AudioInstance; readonly disableWarpDriveSound: AudioInstance; readonly acceleratingWarpDriveSound: AudioInstance; readonly deceleratingWarpDriveSound: AudioInstance; + readonly thrusterSound: AudioInstance; readonly onWarpDriveEnabled = new Observable(); readonly onWarpDriveDisabled = new Observable(); constructor(scene: Scene) { - this.instanceRoot = Assets.CreateSpaceShipInstance(); + this.instanceRoot = Assets.CreateWandererInstance(); setRotationQuaternion(this.instanceRoot, Quaternion.Identity()); this.aggregate = new PhysicsAggregate( @@ -102,6 +106,11 @@ export class Spaceship implements Transformable { scene ); for (const child of this.instanceRoot.getChildMeshes()) { + if(child.name.includes("mainThruster")) { + const mainThruster = new MainThruster(child, getForwardDirection(this.instanceRoot).negate(), this.aggregate); + this.mainThrusters.push(mainThruster); + continue; + } const childShape = new PhysicsShapeMesh(child as Mesh, scene); childShape.filterMembershipMask = CollisionMask.DYNAMIC_OBJECTS; childShape.filterCollideMask = CollisionMask.ENVIRONMENT; @@ -116,31 +125,28 @@ export class Spaceship implements Transformable { console.log("Collision"); if (collisionEvent.impulse < 0.8) return; console.log(collisionEvent); - //Assets.OuchSound.play(); }); - for (const child of this.instanceRoot.getChildMeshes()) { - if (child.name.includes("mainThruster")) { - console.log("Found main thruster"); - this.addMainThruster(child); - } else if (child.name.includes("rcsThruster")) { - console.log("Found rcs thruster"); - this.addRCSThruster(child); - } - } - this.warpTunnel = new WarpTunnel(this.getTransform(), scene); this.hyperSpaceTunnel = new HyperSpaceTunnel(this.getTransform().getDirection(Axis.Z), scene); this.hyperSpaceTunnel.setParent(this.getTransform()); this.hyperSpaceTunnel.setEnabled(false); - this.enableWarpDriveSound = new AudioInstance(Assets.ENABLE_WARP_DRIVE_SOUND, Vector3.Zero(), this.getTransform()); - this.disableWarpDriveSound = new AudioInstance(Assets.DISABLE_WARP_DRIVE_SOUND, Vector3.Zero(), this.getTransform()); - this.acceleratingWarpDriveSound = new AudioInstance(Assets.ACCELERATING_WARP_DRIVE_SOUND, Vector3.Zero(), this.getTransform()); - this.deceleratingWarpDriveSound = new AudioInstance(Assets.DECELERATING_WARP_DRIVE_SOUND, Vector3.Zero(), this.getTransform()); + this.enableWarpDriveSound = new AudioInstance(Assets.ENABLE_WARP_DRIVE_SOUND, AudioMasks.STAR_SYSTEM_VIEW, 1, true, this.getTransform()); + this.disableWarpDriveSound = new AudioInstance(Assets.DISABLE_WARP_DRIVE_SOUND, AudioMasks.STAR_SYSTEM_VIEW, 1, true, this.getTransform()); + this.acceleratingWarpDriveSound = new AudioInstance(Assets.ACCELERATING_WARP_DRIVE_SOUND, AudioMasks.STAR_SYSTEM_VIEW, 0, false, this.getTransform()); + this.deceleratingWarpDriveSound = new AudioInstance(Assets.DECELERATING_WARP_DRIVE_SOUND, AudioMasks.STAR_SYSTEM_VIEW, 0, false, this.getTransform()); + this.thrusterSound = new AudioInstance(Assets.THRUSTER_SOUND, AudioMasks.STAR_SYSTEM_VIEW, 0, false, this.getTransform()); + + AudioManager.RegisterSound(this.enableWarpDriveSound); + AudioManager.RegisterSound(this.disableWarpDriveSound); + AudioManager.RegisterSound(this.acceleratingWarpDriveSound); + AudioManager.RegisterSound(this.deceleratingWarpDriveSound); + AudioManager.RegisterSound(this.thrusterSound); - this.acceleratingWarpDriveSound.setTargetVolume(0); - this.deceleratingWarpDriveSound.setTargetVolume(0); + this.thrusterSound.sound.play(); + this.acceleratingWarpDriveSound.sound.play(); + this.deceleratingWarpDriveSound.sound.play(); this.scene = scene; } @@ -153,20 +159,6 @@ export class Spaceship implements Transformable { return this.aggregate.transformNode; } - private addMainThruster(mesh: AbstractMesh) { - const direction = mesh.getDirection(new Vector3(0, 1, 0)); - this.mainThrusters.push(new MainThruster(mesh, direction, this.aggregate)); - } - - private addRCSThruster(mesh: AbstractMesh) { - const direction = mesh.getDirection(new Vector3(0, 1, 0)); - const thruster = new RCSThruster(mesh, direction, this.aggregate); - this.rcsThrusters.push(thruster); - - //FIXME: this is temporary to balance rc thrust - thruster.setMaxAuthority(thruster.getMaxAuthority() / thruster.leverage); - } - public setEnabled(enabled: boolean, havokPlugin: HavokPlugin) { this.instanceRoot.setEnabled(enabled); setEnabledBody(this.aggregate.body, enabled, havokPlugin); @@ -177,16 +169,13 @@ export class Spaceship implements Transformable { } public enableWarpDrive() { - for (const thruster of this.mainThrusters) thruster.setThrottle(0); - for (const thruster of this.rcsThrusters) thruster.deactivate(); this.warpDrive.enable(); this.aggregate.body.setMotionType(PhysicsMotionType.ANIMATED); this.aggregate.body.setLinearVelocity(Vector3.Zero()); this.aggregate.body.setAngularVelocity(Vector3.Zero()); - if(!this.acceleratingWarpDriveSound.sound.isPlaying) this.acceleratingWarpDriveSound.sound.play(); - if(!this.deceleratingWarpDriveSound.sound.isPlaying) this.deceleratingWarpDriveSound.sound.play(); + this.thrusterSound.setTargetVolume(0); this.enableWarpDriveSound.sound.play(); this.onWarpDriveEnabled.notifyObservers(); @@ -205,6 +194,10 @@ export class Spaceship implements Transformable { else this.disableWarpDrive(); } + public setMainEngineThrottle(throttle: number) { + this.mainEngineThrottle = throttle; + } + /** * Returns a readonly interface to the warp drive of the ship. * @returns A readonly interface to the warp drive of the ship. @@ -232,7 +225,11 @@ export class Spaceship implements Transformable { } public getThrottle(): number { - return this.warpDrive.isEnabled() ? this.warpDrive.getTargetThrottle() : this.mainThrusters[0].getThrottle(); + return this.warpDrive.isEnabled() ? this.warpDrive.getTargetThrottle() : this.mainEngineThrottle; + } + + public increaseMainEngineThrottle(delta: number) { + this.mainEngineThrottle = Math.max(-1, Math.min(1, this.mainEngineThrottle + delta)); } public getClosestWalkableObject(): Transformable | null { @@ -264,6 +261,10 @@ export class Spaceship implements Transformable { this.landingTarget = null; } + public isLanded(): boolean { + return this.state === ShipState.LANDED; + } + private land(deltaTime: number) { if (this.targetLandingPad !== null) { this.landOnPad(this.targetLandingPad, deltaTime); @@ -328,6 +329,8 @@ export class Spaceship implements Transformable { } public update(deltaTime: number) { + this.mainEngineTargetSpeed = this.mainEngineThrottle * 500; + const warpSpeed = getForwardDirection(this.aggregate.transformNode).scale(this.warpDrive.getWarpSpeed()); const currentForwardSpeed = Vector3.Dot(warpSpeed, this.aggregate.transformNode.getDirection(Axis.Z)); @@ -337,12 +340,30 @@ export class Spaceship implements Transformable { 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(); - for (const thruster of this.rcsThrusters) thruster.update(); - if (this.warpDrive.isDisabled()) { - for (const thruster of this.mainThrusters) thruster.applyForce(); - for (const thruster of this.rcsThrusters) thruster.applyForce(); + const linearVelocity = this.aggregate.body.getLinearVelocity(); + const forwardDirection = getForwardDirection(this.getTransform()); + const forwardSpeed = Vector3.Dot(linearVelocity, forwardDirection); + + const otherSpeed = linearVelocity.subtract(forwardDirection.scale(forwardSpeed)); + + if(this.mainEngineThrottle !== 0) this.thrusterSound.setTargetVolume(1); + else this.thrusterSound.setTargetVolume(0); + + if(forwardSpeed < this.mainEngineTargetSpeed) { + this.aggregate.body.applyForce(forwardDirection.scale(3000), this.aggregate.body.getObjectCenterWorld()); + this.mainThrusters.forEach(thruster => { + thruster.setThrottle(this.mainEngineThrottle); + }); + } else { + this.aggregate.body.applyForce(forwardDirection.scale(-3000), this.aggregate.body.getObjectCenterWorld()); + this.mainThrusters.forEach(thruster => { + thruster.setThrottle(0); + }); + } + + // damp other speed + this.aggregate.body.applyForce(otherSpeed.scale(-10), this.aggregate.body.getObjectCenterWorld()); if (this.closestWalkableObject !== null) { const gravityDir = this.closestWalkableObject.getTransform().getAbsolutePosition().subtract(this.getTransform().getAbsolutePosition()).normalize(); @@ -352,8 +373,14 @@ export class Spaceship implements Transformable { this.acceleratingWarpDriveSound.setTargetVolume(0); this.deceleratingWarpDriveSound.setTargetVolume(0); } else { + this.mainThrusters.forEach(thruster => { + thruster.setThrottle(0); + }); + translate(this.getTransform(), warpSpeed.scale(deltaTime)); + this.thrusterSound.setTargetVolume(0); + if (currentForwardSpeed < this.warpDrive.getWarpSpeed()) { this.acceleratingWarpDriveSound.setTargetVolume(1); this.deceleratingWarpDriveSound.setTargetVolume(0); @@ -363,10 +390,9 @@ export class Spaceship implements Transformable { } } - this.enableWarpDriveSound.update(deltaTime); - this.disableWarpDriveSound.update(deltaTime); - this.acceleratingWarpDriveSound.update(deltaTime); - this.deceleratingWarpDriveSound.update(deltaTime); + this.mainThrusters.forEach(thruster => { + thruster.update(deltaTime); + }); if (this.flightAssistEnabled) { this.aggregate.body.setAngularDamping(0.9); diff --git a/src/ts/spaceship/warpDrive.ts b/src/ts/spaceship/warpDrive.ts index bcd72c470..49a71ad6a 100644 --- a/src/ts/spaceship/warpDrive.ts +++ b/src/ts/spaceship/warpDrive.ts @@ -116,7 +116,7 @@ export class WarpDrive implements ReadonlyWarpDrive { */ private state = WarpDriveState.DISABLED; - private static MIN_SPEED = 500; + private static MIN_SPEED = 2000; constructor(enabledByDefault = false) { this.state = enabledByDefault ? WarpDriveState.ENABLED : WarpDriveState.DISABLED; diff --git a/src/ts/spaceshipExtended/thruster.ts b/src/ts/spaceshipExtended/thruster.ts index 76ed31c37..9f542af03 100644 --- a/src/ts/spaceshipExtended/thruster.ts +++ b/src/ts/spaceshipExtended/thruster.ts @@ -20,13 +20,13 @@ import { Mesh, TransformNode } from "@babylonjs/core/Meshes"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; import { PhysicsShapeBox } from "@babylonjs/core/Physics/v2/physicsShape"; import { Scene } from "@babylonjs/core/scene"; -import { DirectionnalParticleSystem } from "../utils/particleSystem"; +import { DirectionalParticleSystem } from "../utils/particleSystem"; export class Thruster { private mesh: Mesh; private torque: Vector3; - private particleSystem: DirectionnalParticleSystem; + private particleSystem: DirectionalParticleSystem; private throttle = 0; @@ -34,7 +34,7 @@ export class Thruster { constructor(mesh: Mesh) { this.mesh = mesh; - this.particleSystem = new DirectionnalParticleSystem(mesh, this.getThrustDirection().negateInPlace()); + this.particleSystem = new DirectionalParticleSystem(mesh, this.getThrustDirection().negateInPlace()); const minY = this.mesh.getBoundingInfo().boundingBox.extendSize.y; this.particleSystem.minEmitBox = new Vector3(-0.8, -minY, -0.8); diff --git a/src/ts/spacestation/spaceStation.ts b/src/ts/spacestation/spaceStation.ts index 2a17f7b8c..c92202142 100644 --- a/src/ts/spacestation/spaceStation.ts +++ b/src/ts/spacestation/spaceStation.ts @@ -155,7 +155,7 @@ export class SpaceStation implements OrbitalObject, Cullable { } public getBoundingRadius(): number { - return 1e3; + return 2e3; } getTypeName(): string { diff --git a/src/ts/starSystem/starSystemModel.ts b/src/ts/starSystem/starSystemModel.ts index c890ff503..5109144a6 100644 --- a/src/ts/starSystem/starSystemModel.ts +++ b/src/ts/starSystem/starSystemModel.ts @@ -23,12 +23,12 @@ import { generateName } from "../utils/nameGenerator"; import { SystemSeed } from "../utils/systemSeed"; enum GenerationSteps { - Name, - NbStars = 20, - GenerateStars = 21, - NbPlanets = 30, - GeneratePlanets = 200, - ChoosePlanetType = 200 + NAME, + NB_STARS = 20, + GENERATE_STARS = 21, + NB_PLANETS = 30, + GENERATE_PLANETS = 200, + CHOOSE_PLANET_TYPE = 200 } export class StarSystemModel { @@ -41,7 +41,7 @@ export class StarSystemModel { this.seed = seed; this.rng = seededSquirrelNoise(this.seed.hash); - this.name = generateName(this.rng, GenerationSteps.Name); + this.name = generateName(this.rng, GenerationSteps.NAME); } setName(name: string) { @@ -59,12 +59,12 @@ export class StarSystemModel { getNbPlanets(): number { if (this.getBodyTypeOfStar(0) === BodyType.BLACK_HOLE) return 0; //Fixme: will not apply when more than one star - return randRangeInt(0, 7, this.rng, GenerationSteps.NbPlanets); + return randRangeInt(0, 7, this.rng, GenerationSteps.NB_PLANETS); } public getStarSeed(index: number) { if (index > this.getNbStars()) throw new Error("Star out of bound! " + index); - return centeredRand(this.rng, GenerationSteps.GenerateStars + index) * Settings.SEED_HALF_RANGE; + return centeredRand(this.rng, GenerationSteps.GENERATE_STARS + index) * Settings.SEED_HALF_RANGE; } /** @@ -76,18 +76,18 @@ export class StarSystemModel { if (index > this.getNbStars()) throw new Error("Star out of bound! " + index); // percentages are taken from https://physics.stackexchange.com/questions/442154/how-common-are-neutron-stars - if (uniformRandBool(0.0006, this.rng, GenerationSteps.GenerateStars + index)) return BodyType.BLACK_HOLE; - if (uniformRandBool(0.0026, this.rng, GenerationSteps.GenerateStars + index)) return BodyType.NEUTRON_STAR; + if (uniformRandBool(0.0006, this.rng, GenerationSteps.GENERATE_STARS + index)) return BodyType.BLACK_HOLE; + if (uniformRandBool(0.0026, this.rng, GenerationSteps.GENERATE_STARS + index)) return BodyType.NEUTRON_STAR; return BodyType.STAR; } public getBodyTypeOfPlanet(index: number) { - if (uniformRandBool(0.5, this.rng, GenerationSteps.ChoosePlanetType + index)) return BodyType.TELLURIC_PLANET; + if (uniformRandBool(0.5, this.rng, GenerationSteps.CHOOSE_PLANET_TYPE + index)) return BodyType.TELLURIC_PLANET; return BodyType.GAS_PLANET; } public getPlanetSeed(index: number) { - return centeredRand(this.rng, GenerationSteps.GeneratePlanets + index) * Settings.SEED_HALF_RANGE; + return centeredRand(this.rng, GenerationSteps.GENERATE_PLANETS + index) * Settings.SEED_HALF_RANGE; } } diff --git a/src/ts/starSystem/starSystemView.ts b/src/ts/starSystem/starSystemView.ts index 3de9a0cf4..8ea1016ba 100644 --- a/src/ts/starSystem/starSystemView.ts +++ b/src/ts/starSystem/starSystemView.ts @@ -83,7 +83,7 @@ export class StarSystemView implements View { StarSystemView.UN_ZOOM_ANIMATION.setKeys([ { frame: 0, - value: 30 + value: ShipControls.BASE_CAMERA_RADIUS }, { frame: 30, @@ -225,6 +225,7 @@ export class StarSystemView implements View { Assets.GRASS_MATERIAL.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaSeconds); this.chunkForge.update(); + starSystem.update(deltaSeconds, this.chunkForge); if (this.spaceshipControls === null) throw new Error("Spaceship controls is null"); @@ -299,8 +300,7 @@ export class StarSystemView implements View { shipControls.spaceship.warpTunnel.setThrottle(0); shipControls.spaceship.setEnabled(false, this.havokPlugin); - shipControls.spaceship.acceleratingWarpDriveSound.setTargetVolume(0); - shipControls.spaceship.deceleratingWarpDriveSound.setTargetVolume(0); + this.stopBackgroundSounds(); } switchToDefaultControls() { @@ -311,8 +311,7 @@ export class StarSystemView implements View { characterControls.getTransform().setEnabled(false); shipControls.spaceship.warpTunnel.setThrottle(0); shipControls.spaceship.setEnabled(false, this.havokPlugin); - shipControls.spaceship.acceleratingWarpDriveSound.setTargetVolume(0); - shipControls.spaceship.deceleratingWarpDriveSound.setTargetVolume(0); + this.stopBackgroundSounds(); this.scene.setActiveController(defaultControls); setRotationQuaternion(defaultControls.getTransform(), getRotationQuaternion(shipControls.getTransform()).clone()); @@ -320,8 +319,9 @@ export class StarSystemView implements View { } stopBackgroundSounds() { - this.spaceshipControls?.spaceship.acceleratingWarpDriveSound.muteInstantly(); - this.spaceshipControls?.spaceship.deceleratingWarpDriveSound.muteInstantly(); + this.spaceshipControls?.spaceship.acceleratingWarpDriveSound.setTargetVolume(0); + this.spaceshipControls?.spaceship.deceleratingWarpDriveSound.setTargetVolume(0); + this.spaceshipControls?.spaceship.thrusterSound.setTargetVolume(0); } /** diff --git a/src/ts/starmap/starMap.ts b/src/ts/starmap/starMap.ts index f3ffb2272..f4ef6e888 100644 --- a/src/ts/starmap/starMap.ts +++ b/src/ts/starmap/starMap.ts @@ -52,11 +52,16 @@ import { NeutronStarModel } from "../stellarObjects/neutronStar/neutronStarModel import { View } from "../utils/view"; import { Assets } from "../assets"; import { syncCamera } from "../utils/cameraSyncing"; +import { AudioInstance } from "../utils/audioInstance"; +import { AudioManager } from "../audio/audioManager"; +import { AudioMasks } from "../audio/audioMasks"; export class StarMap implements View { readonly scene: Scene; private readonly controls: DefaultControls; + private readonly backgroundMusic: AudioInstance; + private rotationAnimation: TransformRotationAnimation | null = null; private translationAnimation: TransformTranslationAnimation | null = null; @@ -124,6 +129,10 @@ export class StarMap implements View { this.controls.getActiveCamera().attachControl(); + this.backgroundMusic = new AudioInstance(Assets.STAR_MAP_BACKGROUND_MUSIC, AudioMasks.STAR_MAP_VIEW, 1, false, null); + AudioManager.RegisterSound(this.backgroundMusic); + this.backgroundMusic.sound.play(); + this.starMapUI = new StarMapUI(engine); this.starMapUI.warpButton.onPointerClickObservable.add(() => { @@ -509,14 +518,6 @@ export class StarMap implements View { this.starMapUI.setHoveredStarSystemMesh(null); } - startBackgroundMusic() { - Assets.STAR_MAP_BACKGROUND_MUSIC.play(); - } - - stopBackgroundMusic() { - Assets.STAR_MAP_BACKGROUND_MUSIC.stop(); - } - public focusOnCurrentSystem(skipAnimation = false) { console.log("focus on current system"); if (this.currentSystemSeed === null) return console.warn("No current system seed!"); diff --git a/src/ts/ui/objectOverlay.ts b/src/ts/ui/objectOverlay.ts index 8412943fc..f71358ed1 100644 --- a/src/ts/ui/objectOverlay.ts +++ b/src/ts/ui/objectOverlay.ts @@ -36,7 +36,7 @@ export class ObjectOverlay { readonly etaText: TextBlock; readonly object: OrbitalObject; - private lastDistance: number = 0; + private lastDistance = 0; static readonly WIDTH = 300; diff --git a/src/ts/ui/systemUI.ts b/src/ts/ui/systemUI.ts index 3c6f72504..4654db05b 100644 --- a/src/ts/ui/systemUI.ts +++ b/src/ts/ui/systemUI.ts @@ -23,7 +23,6 @@ import { OrbitalObject } from "../architecture/orbitalObject"; import { Engine } from "@babylonjs/core/Engines/engine"; import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { Assets } from "../assets"; export class SystemUI { readonly scene: Scene; diff --git a/src/ts/utils/audioInstance.ts b/src/ts/utils/audioInstance.ts index 499a49520..312e8bafb 100644 --- a/src/ts/utils/audioInstance.ts +++ b/src/ts/utils/audioInstance.ts @@ -6,7 +6,7 @@ import { TransformNode } from "@babylonjs/core/Meshes"; export class AudioInstance { readonly sound: Sound; - private targetVolume = 1; + private targetVolume; private targetPlaybackSpeed = 1; private blendSpeed = 0.5; @@ -20,19 +20,34 @@ export class AudioInstance { private parent: TransformNode | null; - constructor(baseSound: Sound, localPosition: Vector3, parent: TransformNode | null) { + private maskFactor = 1; + + private targetMaskFactor = 1; + + private readonly isPonctual: boolean; + + readonly mask: number; + + constructor(baseSound: Sound, mask: number, initialTargetVolume: number, isPonctual: boolean, parent: TransformNode | null) { const clonedSound = baseSound.clone(); if (clonedSound === null) throw new Error("Cloned sound was null!"); this.sound = clonedSound; + this.mask = mask; + + this.targetVolume = initialTargetVolume; + this.volumeMultiplier = baseSound.getVolume(); this.playbackSpeedMultiplier = baseSound.getPlaybackRate(); this.sound.updateOptions({ - playbackRate: this.playbackSpeedMultiplier + playbackRate: this.playbackSpeedMultiplier * this.targetPlaybackSpeed, + volume: this.targetVolume * this.volumeMultiplier * this.maskFactor }); - this.localPosition = localPosition; + this.isPonctual = isPonctual; + + this.localPosition = Vector3.Zero(); this.parent = parent; } @@ -53,26 +68,31 @@ export class AudioInstance { this.blendSpeed = speed; } - muteInstantly() { - this.sound.setVolume(0); + setMaskFactor(factor: number) { + this.targetMaskFactor = factor; } update(deltaSeconds: number) { - if (this.blendSpeed === 0) { - this.sound.setVolume(this.targetVolume * this.volumeMultiplier); - this.sound.setPlaybackRate(this.targetPlaybackSpeed); - } else { - this.sound.setVolume(moveTowards(this.sound.getVolume(), this.targetVolume * this.volumeMultiplier * this.spatialVolumeMultiplier, this.blendSpeed * deltaSeconds)); - this.sound.setPlaybackRate(moveTowards(this.sound.getPlaybackRate(), this.targetPlaybackSpeed * this.playbackSpeedMultiplier, this.blendSpeed * deltaSeconds)); + this.maskFactor = moveTowards(this.maskFactor, this.targetMaskFactor, this.blendSpeed * deltaSeconds); + if (this.maskFactor === 0) { + this.sound.stop(); + } else if (!this.sound.isPlaying && !this.isPonctual) { + this.sound.play(); } - if (this.parent !== null) { + this.sound.setVolume( + moveTowards(this.sound.getVolume(), this.targetVolume * this.volumeMultiplier * this.spatialVolumeMultiplier * this.maskFactor, this.blendSpeed * deltaSeconds) + ); + this.sound.setPlaybackRate(moveTowards(this.sound.getPlaybackRate(), this.targetPlaybackSpeed * this.playbackSpeedMultiplier, this.blendSpeed * deltaSeconds)); + + + /*if (this.parent !== null) { const worldPosition = Vector3.TransformCoordinates(this.localPosition, this.parent.getWorldMatrix()); const camera = this.parent.getScene().activeCamera; - if(camera === null) throw new Error("No active camera"); + if (camera === null) throw new Error("No active camera"); const worldCameraPosition = Vector3.TransformCoordinates(camera.position, this.parent.getWorldMatrix()); const distance = Vector3.Distance(worldCameraPosition, worldPosition); this.spatialVolumeMultiplier = 1 / (1 + 0.01 * distance); - } + }*/ } } diff --git a/src/ts/utils/parseToStrings.ts b/src/ts/utils/parseToStrings.ts index 0e8854563..7ff8e8d47 100644 --- a/src/ts/utils/parseToStrings.ts +++ b/src/ts/utils/parseToStrings.ts @@ -63,7 +63,7 @@ export function parsePercentageFrom01(percentage01: number): string { return `${(percentage01 * 100).toFixed(0)}%`; } -export const alphabet = "abcdefghijklmnopqrstuvwxyz"; +export const Alphabet = "abcdefghijklmnopqrstuvwxyz"; export function starName(baseName: string, index: number): string { - return `${baseName} ${alphabet[index].toUpperCase()}`; + return `${baseName} ${Alphabet[index].toUpperCase()}`; } diff --git a/src/ts/utils/particleSystem.ts b/src/ts/utils/particleSystem.ts index f7f2e7268..ac38aea69 100644 --- a/src/ts/utils/particleSystem.ts +++ b/src/ts/utils/particleSystem.ts @@ -26,7 +26,7 @@ function randomNumber(min: number, max: number): number { return Math.random() * (max - min) + min; } -export class DirectionnalParticleSystem extends ParticleSystem { +export class DirectionalParticleSystem extends ParticleSystem { private direction: Vector3; readonly emitter: AbstractMesh; @@ -47,14 +47,13 @@ export class DirectionnalParticleSystem extends ParticleSystem { this.particleTexture.hasAlpha = true; this.emitter = mesh; - this.minSize = 0.6; - this.maxSize = 0.7; + this.minSize = 1.6; + this.maxSize = 1.7; this.minLifeTime = 0.5; this.maxLifeTime = 0.6; this.minEmitPower = 0; this.maxEmitPower = 0; this.updateSpeed = 0.005; - this.forceDepthWrite = true; this.color1 = new Color4(0.5, 0.5, 0.5, 1); this.color2 = new Color4(0.5, 0.5, 0.5, 1); this.colorDead = new Color4(0, 0, 0, 0); diff --git a/src/ts/utils/solidPlume.ts b/src/ts/utils/solidPlume.ts new file mode 100644 index 000000000..a5f1a2f6f --- /dev/null +++ b/src/ts/utils/solidPlume.ts @@ -0,0 +1,149 @@ +import { SolidParticleSystem } from "@babylonjs/core/Particles/solidParticleSystem"; +import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder"; +import { Scene } from "@babylonjs/core/scene"; +import { Axis } from "@babylonjs/core/Maths/math.axis"; +import { Vector3 } from "@babylonjs/core/Maths/math.vector"; +import { SolidParticle } from "@babylonjs/core/Particles/solidParticle"; +import { Scalar } from "@babylonjs/core/Maths/math.scalar"; +import { StandardMaterial } from "@babylonjs/core/Materials/standardMaterial"; +import { Color3, Color4 } from "@babylonjs/core/Maths/math.color"; +import { TransformNode } from "@babylonjs/core/Meshes"; + +export class SolidPlume { + static TUNNEL_LENGTH = 3; + static MAX_NB_PARTICLES = 3000; + + targetNbParticles = 0; + nbParticles = 0; + + direction = Axis.Z; + + recycledParticles: SolidParticle[] = []; + + SPS: SolidParticleSystem; + + private nbSubTimeSteps = 5; + private subTimeStep = 0; + + constructor(engineAnchor: TransformNode, scene: Scene) { + this.SPS = new SolidParticleSystem("SPS", scene); + const poly = MeshBuilder.CreatePolyhedron("p", { type: 1 }); + this.SPS.addShape(poly, SolidPlume.MAX_NB_PARTICLES); + + // trying to get rid of polygon + // poly.setEnabled(false); + // poly.visibility = 0; + poly.dispose(); //dispose of original model poly + + this.SPS.buildMesh(); // finally builds and displays the SPS mesh + this.SPS.isAlwaysVisible = true; + + const scaling = new Vector3(0.01, 0.01, 0.1); + + // initiate particles function + this.SPS.initParticles = () => { + for (let p = 0; p < this.SPS.nbParticles; p++) { + const particle = this.SPS.particles[p]; + this.initParticle(particle); + if (this.nbParticles >= this.targetNbParticles) { + // particle.alive = false; + particle.isVisible = false; + this.recycledParticles.push(particle); + // console.log('set particle', particle.name, 'as not alive'); + } else { + this.nbParticles++; + } + particle.position.z = Scalar.RandomRange(0, SolidPlume.TUNNEL_LENGTH); + } + }; + + //Update SPS mesh + this.SPS.initParticles(); + this.SPS.setParticles(); + + const mat = new StandardMaterial("mat", scene); + mat.emissiveColor = new Color3(1, 1, 1); + mat.disableLighting = true; + this.SPS.mesh.material = mat; + + this.SPS.updateParticle = (particle) => { + // console.log('particle', particle.name, 'alive', particle.alive); + if (!particle.alive) return particle; + if (!particle.isVisible) return particle; + particle.position.addInPlace(particle.velocity.scale(10 * this.subTimeStep)); + + const localZ = particle.position.z; + + // if particle has gone too far, recycle or reset depending on current need + if (localZ > SolidPlume.TUNNEL_LENGTH) { + if (this.nbParticles <= this.targetNbParticles) { + this.initParticle(particle); + } else { + this.recycledParticles.push(particle); + particle.isVisible = false; + //particle.alive = false; + this.nbParticles--; + + return particle; + } + } + + // update color and scaling + + const progression = Scalar.RangeToPercent(localZ, 0, SolidPlume.TUNNEL_LENGTH); + + if (progression < 0.5) { + const t = progression / 0.5; + particle.color = Color4.Lerp(new Color4(0, 0, 1, 1), new Color4(0, 1, 1, 1), t); + particle.scaling = scaling; //Vector3.Lerp(Vector3.Zero(), scaling, Math.min(t * 2, 1)); + } else { + const t = (progression - 0.5) / 0.5; + particle.color = Color4.Lerp(new Color4(0, 1, 1), new Color4(1, 0, 1), t); + particle.scaling = Vector3.Lerp(scaling, Vector3.Zero(), t * t); + } + // console.log('particle scaling is', particle.scaling.toString()); + + return particle; + }; + + this.SPS.mesh.parent = engineAnchor; + this.SPS.mesh.scaling.z = -1; + } + + private initParticle(particle: SolidParticle) { + const r = Math.sqrt(Math.random()); + const theta = Math.random() * 2 * Math.PI; + + const position = new Vector3(r * Math.cos(theta), r * Math.sin(theta), 0); + + particle.position.copyFrom(position); + + particle.velocity.copyFrom(this.direction.scale(2)); + + // particle.scaling = scaling; + particle.scaling = new Vector3(0, 0, 0); + } + + setThrottle(throttle: number) { + this.targetNbParticles = Math.floor(SolidPlume.MAX_NB_PARTICLES * throttle); + } + + update(deltaSeconds: number) { + this.subTimeStep = deltaSeconds / this.nbSubTimeSteps; + for (let i = 0; i < this.nbSubTimeSteps; i++) { + // if there aren't enough particles, instantiate more + for (let j = 0; j < 10; j++) { + if (this.nbParticles < this.targetNbParticles && this.recycledParticles.length > 0) { + const particle = this.recycledParticles.shift() as SolidParticle; + // particle.alive = true; + particle.isVisible = true; + // console.log('make particle', particle.name, 'alive'); + this.initParticle(particle); + this.nbParticles++; + } + } + + this.SPS.setParticles(); + } + } +} diff --git a/src/ts/utils/warpTunnel.ts b/src/ts/utils/warpTunnel.ts index 5b3c46046..dd9068563 100644 --- a/src/ts/utils/warpTunnel.ts +++ b/src/ts/utils/warpTunnel.ts @@ -39,7 +39,7 @@ export class WarpTunnel implements Transformable { readonly solidParticleSystem: SolidParticleSystem; - private throttle: number = 0; + private throttle = 0; static TUNNEL_LENGTH = 300;