diff --git a/src/shaders/starfieldFragment.glsl b/src/shaders/starfieldFragment.glsl index 2b35f8cdd..70232c824 100644 --- a/src/shaders/starfieldFragment.glsl +++ b/src/shaders/starfieldFragment.glsl @@ -32,6 +32,10 @@ void main() { vec2 starfieldUV = vec2(0.0); + // Here, a color test is used and not a depth test + // You may wonder why. The answer is that using a depth test wouldn't account for the 2D UI and the starfield would be drawn on top of it. + // In fact the UI has no depth information, so we need to use something else. I chose this color test as it works in practice but it could break. + // If you have a better idea, please let me know or make a pull request. if (screenColor == vec4(0.0)) { // get the starfield color // get spherical coordinates uv for the starfield texture diff --git a/src/ts/index.ts b/src/ts/index.ts index 208247dec..5815a6c39 100644 --- a/src/ts/index.ts +++ b/src/ts/index.ts @@ -21,7 +21,6 @@ import { Mouse } from "./inputs/mouse"; import { Keyboard } from "./inputs/keyboard"; import { StarModel } from "./stellarObjects/star/starModel"; import { RingsUniforms } from "./postProcesses/rings/ringsUniform"; -import { SpaceStation } from "./spacestation/spaceStation"; import { getMoonSeed } from "./planemos/common"; import { Gamepad } from "./inputs/gamepad"; @@ -132,6 +131,7 @@ const planet = StarSystemHelper.makeTelluricPlanet(starSystem, planetModel); planet.model.ringsUniforms = new RingsUniforms(planet.model.rng); planet.postProcesses.push(PostProcessType.RING); + //const spacestation = new SpaceStation(starSystemView.scene, planet); //starSystemView.getStarSystem().addSpaceStation(spacestation); diff --git a/src/ts/physicSpaceship.ts b/src/ts/physicSpaceship.ts index a9f595544..70831cd0a 100644 --- a/src/ts/physicSpaceship.ts +++ b/src/ts/physicSpaceship.ts @@ -29,7 +29,7 @@ import { translate } from "./uberCore/transforms/basicTransform"; import { StarModel } from "./stellarObjects/star/starModel"; import { Keyboard } from "./inputs/keyboard"; import { Star } from "./stellarObjects/star/star"; -import { ChunkForge } from "./planemos/telluricPlanemo/terrain/chunks/chunkForge"; +import { ChunkForgeWorkers } from "./planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers"; const canvas = document.getElementById("renderer") as HTMLCanvasElement; canvas.width = window.innerWidth; @@ -63,7 +63,7 @@ hemiLight.intensity = 0.2; const shadowGenerator = new ShadowGenerator(1024, light); shadowGenerator.useBlurExponentialShadowMap = true; -const chunkForge = new ChunkForge(Settings.VERTEX_RESOLUTION); +const chunkForge = new ChunkForgeWorkers(Settings.VERTEX_RESOLUTION); const keyboard = new Keyboard(); diff --git a/src/ts/planemos/telluricPlanemo/telluricPlanemo.ts b/src/ts/planemos/telluricPlanemo/telluricPlanemo.ts index ef674bb34..ed8e8a787 100644 --- a/src/ts/planemos/telluricPlanemo/telluricPlanemo.ts +++ b/src/ts/planemos/telluricPlanemo/telluricPlanemo.ts @@ -12,9 +12,11 @@ import { TelluricPlanemoModel } from "./telluricPlanemoModel"; import { PostProcessType } from "../../postProcesses/postProcessTypes"; import { Camera } from "@babylonjs/core/Cameras/camera"; import { ChunkTree } from "./terrain/chunks/chunkTree"; -import { ChunkForge } from "./terrain/chunks/chunkForge"; import { PhysicsShapeSphere } from "@babylonjs/core/Physics/v2/physicsShape"; import { Transformable } from "../../uberCore/transforms/basicTransform"; +import { ChunkForge } from "./terrain/chunks/chunkForge"; +import { Observable } from "@babylonjs/core/Misc/observable"; +import { PlanetChunk } from "./terrain/chunks/planetChunk"; export class TelluricPlanemo extends AbstractBody implements Planemo, PlanemoMaterial { readonly sides: ChunkTree[]; // stores the 6 sides of the sphere @@ -23,6 +25,8 @@ export class TelluricPlanemo extends AbstractBody implements Planemo, PlanemoMat readonly model: TelluricPlanemoModel; + readonly onChunkCreatedObservable = new Observable(); + /** * New Telluric Planet * @param name The name of the planet @@ -70,13 +74,7 @@ export class TelluricPlanemo extends AbstractBody implements Planemo, PlanemoMat new ChunkTree(Direction.Left, this.name, this.model, this.aggregate, this.material, scene) ]; - for (const side of this.sides) { - side.onChunkPhysicsShapeDeletedObservable.add((index) => { - for (const side2 of this.sides) { - side2.registerPhysicsShapeDeletion(index); - } - }); - } + this.sides.forEach((side) => side.onChunkCreatedObservable.add((chunk) => this.onChunkCreatedObservable.notifyObservers(chunk))); } getTypeName(): string { diff --git a/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForge.ts b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForge.ts index 5df36abb1..96be5dac3 100644 --- a/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForge.ts +++ b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForge.ts @@ -1,112 +1,6 @@ -import { TransferBuildData } from "./workerDataTypes"; -import { ApplyTask, BuildTask, ReturnedChunkData, TaskType } from "./taskTypes"; -import { WorkerPool } from "./workerPool"; -import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData"; +import { BuildTask } from "./taskTypes"; -export class ChunkForge { - /** - * the number vertices per row of the chunk (total number of vertices = nbVerticesPerRow * nbVerticesPerRow) - */ - nbVerticesPerRow: number; - - /** - * The worker manager - * FIXME: the workerpool does not need to be a class - */ - workerPool: WorkerPool; - - /** - * The queue of tasks containing chunks ready to be enabled - */ - applyTaskQueue: ApplyTask[] = []; - - constructor(nbVerticesPerRow: number) { - this.nbVerticesPerRow = nbVerticesPerRow; - const nbMaxWorkers = navigator.hardwareConcurrency - 1; // -1 because the main thread is also used - this.workerPool = new WorkerPool(nbMaxWorkers); - } - - public addTask(task: BuildTask) { - this.workerPool.submitTask(task); - } - - /** - * Executes the next task using an available worker - * @param worker the web worker assigned to the next task - */ - private executeNextTask(worker: Worker) { - if (this.workerPool.hasTask()) this.dispatchBuildTask(this.workerPool.nextTask(), worker); - else this.workerPool.finishedWorkers.push(worker); - } - - private dispatchBuildTask(task: BuildTask, worker: Worker): void { - const buildData: TransferBuildData = { - taskType: TaskType.Build, - planetName: task.planetName, - planetDiameter: task.planetDiameter, - nbVerticesPerSide: this.nbVerticesPerRow, - depth: task.depth, - direction: task.direction, - position: [task.position.x, task.position.y, task.position.z], - terrainSettings: { - continents_frequency: task.terrainSettings.continents_frequency, - continents_fragmentation: task.terrainSettings.continents_fragmentation, - continent_base_height: task.terrainSettings.continent_base_height, - max_mountain_height: task.terrainSettings.max_mountain_height, - max_bump_height: task.terrainSettings.max_bump_height, - bumps_frequency: task.terrainSettings.bumps_frequency, - mountains_frequency: task.terrainSettings.mountains_frequency - }, - seed: task.planetSeed - }; - - worker.postMessage(buildData); - - worker.onmessage = (e) => { - const data: ReturnedChunkData = e.data; - - const vertexData = new VertexData(); - vertexData.positions = data.positions; - vertexData.normals = data.normals; - vertexData.indices = data.indices; - - const applyTask: ApplyTask = { - type: TaskType.Apply, - vertexData: vertexData, - chunk: task.chunk, - instancesMatrixBuffer: data.instancesMatrixBuffer, - alignedInstancesMatrixBuffer: data.alignedInstancesMatrixBuffer, - averageHeight: data.averageHeight - }; - this.applyTaskQueue.push(applyTask); - - if (this.workerPool.hasTask()) this.dispatchBuildTask(this.workerPool.nextTask(), worker); - else this.workerPool.finishedWorkers.push(worker); - }; - } - - /** - * Apply generated vertexData to waiting chunks - */ - private executeNextApplyTask() { - let task = this.applyTaskQueue.shift(); - while (task !== undefined && task.chunk.hasBeenDisposed()) { - // if the chunk has been disposed, we skip it - task = this.applyTaskQueue.shift(); - } - if (task) task.chunk.init(task.vertexData, task.instancesMatrixBuffer, task.alignedInstancesMatrixBuffer, task.averageHeight); - } - - /** - * Updates the state of the forge : dispatch tasks to workers, remove useless chunks, apply vertexData to new chunks - */ - public update() { - for (let i = 0; i < this.workerPool.availableWorkers.length; i++) { - this.executeNextTask(this.workerPool.availableWorkers.shift() as Worker); - } - this.workerPool.availableWorkers = this.workerPool.availableWorkers.concat(this.workerPool.finishedWorkers); - this.workerPool.finishedWorkers = []; - - this.executeNextApplyTask(); - } +export interface ChunkForge { + addTask(task: BuildTask): void; + update(): void; } diff --git a/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers.ts b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers.ts new file mode 100644 index 000000000..32f52054c --- /dev/null +++ b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers.ts @@ -0,0 +1,113 @@ +import { TransferBuildData } from "./workerDataTypes"; +import { ApplyTask, BuildTask, ReturnedChunkData, TaskType } from "./taskTypes"; +import { WorkerPool } from "./workerPool"; +import { VertexData } from "@babylonjs/core/Meshes/mesh.vertexData"; +import { ChunkForge } from "./chunkForge"; + +export class ChunkForgeWorkers implements ChunkForge { + /** + * the number vertices per row of the chunk (total number of vertices = nbVerticesPerRow * nbVerticesPerRow) + */ + nbVerticesPerRow: number; + + /** + * The worker manager + * FIXME: the workerpool does not need to be a class + */ + workerPool: WorkerPool; + + /** + * The queue of tasks containing chunks ready to be enabled + */ + applyTaskQueue: ApplyTask[] = []; + + constructor(nbVerticesPerRow: number) { + this.nbVerticesPerRow = nbVerticesPerRow; + const nbMaxWorkers = navigator.hardwareConcurrency - 1; // -1 because the main thread is also used + this.workerPool = new WorkerPool(nbMaxWorkers); + } + + public addTask(task: BuildTask) { + this.workerPool.submitTask(task); + } + + /** + * Executes the next task using an available worker + * @param worker the web worker assigned to the next task + */ + private executeNextTask(worker: Worker) { + if (this.workerPool.hasTask()) this.dispatchBuildTask(this.workerPool.nextTask(), worker); + else this.workerPool.finishedWorkers.push(worker); + } + + private dispatchBuildTask(task: BuildTask, worker: Worker): void { + const buildData: TransferBuildData = { + taskType: TaskType.Build, + planetName: task.planetName, + planetDiameter: task.planetDiameter, + nbVerticesPerSide: this.nbVerticesPerRow, + depth: task.depth, + direction: task.direction, + position: [task.position.x, task.position.y, task.position.z], + terrainSettings: { + continents_frequency: task.terrainSettings.continents_frequency, + continents_fragmentation: task.terrainSettings.continents_fragmentation, + continent_base_height: task.terrainSettings.continent_base_height, + max_mountain_height: task.terrainSettings.max_mountain_height, + max_bump_height: task.terrainSettings.max_bump_height, + bumps_frequency: task.terrainSettings.bumps_frequency, + mountains_frequency: task.terrainSettings.mountains_frequency + }, + seed: task.planetSeed + }; + + worker.postMessage(buildData); + + worker.onmessage = (e) => { + const data: ReturnedChunkData = e.data; + + const vertexData = new VertexData(); + vertexData.positions = data.positions; + vertexData.normals = data.normals; + vertexData.indices = data.indices; + + const applyTask: ApplyTask = { + type: TaskType.Apply, + vertexData: vertexData, + chunk: task.chunk, + instancesMatrixBuffer: data.instancesMatrixBuffer, + alignedInstancesMatrixBuffer: data.alignedInstancesMatrixBuffer, + averageHeight: data.averageHeight + }; + this.applyTaskQueue.push(applyTask); + + if (this.workerPool.hasTask()) this.dispatchBuildTask(this.workerPool.nextTask(), worker); + else this.workerPool.finishedWorkers.push(worker); + }; + } + + /** + * Apply generated vertexData to waiting chunks + */ + private executeNextApplyTask() { + let task = this.applyTaskQueue.shift(); + while (task !== undefined && task.chunk.hasBeenDisposed()) { + // if the chunk has been disposed, we skip it + task = this.applyTaskQueue.shift(); + } + if (task) task.chunk.init(task.vertexData, task.instancesMatrixBuffer, task.alignedInstancesMatrixBuffer, task.averageHeight); + } + + /** + * Updates the state of the forge : dispatch tasks to workers, remove useless chunks, apply vertexData to new chunks + */ + public update() { + for (let i = 0; i < this.workerPool.availableWorkers.length; i++) { + this.executeNextTask(this.workerPool.availableWorkers.shift() as Worker); + } + this.workerPool.availableWorkers = this.workerPool.availableWorkers.concat(this.workerPool.finishedWorkers); + this.workerPool.finishedWorkers = []; + + this.executeNextApplyTask(); + } +} diff --git a/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkTree.ts b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkTree.ts index ba3d83683..31b97fa71 100644 --- a/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkTree.ts +++ b/src/ts/planemos/telluricPlanemo/terrain/chunks/chunkTree.ts @@ -1,6 +1,5 @@ import { PlanetChunk } from "./planetChunk"; import { Direction } from "../../../../utils/direction"; -import { ChunkForge } from "./chunkForge"; import { BuildTask, TaskType } from "./taskTypes"; import { Settings } from "../../../../settings"; import { getChunkSphereSpacePositionFromPath } from "../../../../utils/chunkUtils"; @@ -11,11 +10,11 @@ import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; import { TransformNode } from "@babylonjs/core/Meshes"; import { Camera } from "@babylonjs/core/Cameras/camera"; -import { isSizeOnScreenEnough } from "../../../../utils/isObjectVisibleOnScreen"; import { Observable } from "@babylonjs/core/Misc/observable"; import { DeleteSemaphore } from "./deleteSemaphore"; import { UberScene } from "../../../../uberCore/uberScene"; import { getRotationQuaternion } from "../../../../uberCore/transforms/basicTransform"; +import { ChunkForge } from "./chunkForge"; /** * A quadTree is defined recursively @@ -48,7 +47,7 @@ export class ChunkTree { readonly parent: TransformNode; readonly parentAggregate: PhysicsAggregate; - readonly onChunkPhysicsShapeDeletedObservable = new Observable(); + readonly onChunkCreatedObservable = new Observable(); readonly material: Material; @@ -61,7 +60,7 @@ export class ChunkTree { * @param material * @param scene */ - constructor(direction: Direction, planetName: string, planetModel: TelluricPlanemoModel, parentAggregate: PhysicsAggregate, material: Material, scene: UberScene) { + constructor(direction: Direction, planetName: string, planetModel: TelluricPlanemoModel, parentAggregate: PhysicsAggregate, material: Material, scene: UberScene) { this.rootChunkLength = planetModel.radius * 2; this.planetName = planetName; this.planetSeed = planetModel.seed; @@ -189,6 +188,7 @@ export class ChunkTree { if (tree instanceof PlanetChunk) { if (!tree.isReady()) return tree; if (!tree.mesh.isVisible) return tree; + if (!tree.mesh.isEnabled()) return tree; } const newTree = [ @@ -215,8 +215,8 @@ export class ChunkTree { private createChunk(path: number[], chunkForge: ChunkForge): PlanetChunk { const chunk = new PlanetChunk(path, this.direction, this.parentAggregate, this.material, this.planetModel, this.rootChunkLength, this.scene); - chunk.onDestroyPhysicsShapeObservable.add((index) => { - this.onChunkPhysicsShapeDeletedObservable.notifyObservers(index); + chunk.onRecieveVertexDataObservable.add(() => { + this.onChunkCreatedObservable.notifyObservers(chunk); }); const buildTask: BuildTask = { @@ -236,17 +236,6 @@ export class ChunkTree { return chunk; } - public registerPhysicsShapeDeletion(index: number): void { - this.executeOnEveryChunk((chunk) => { - chunk.registerPhysicsShapeDeletion(index); - }); - for (const deleteSemaphore of this.deleteSemaphores) { - for (const chunk of deleteSemaphore.chunksToDelete) { - chunk.registerPhysicsShapeDeletion(index); - } - } - } - public computeCulling(camera: Camera): void { this.executeOnEveryChunk((chunk: PlanetChunk) => { chunk.computeCulling(camera); diff --git a/src/ts/planemos/telluricPlanemo/terrain/chunks/planetChunk.ts b/src/ts/planemos/telluricPlanemo/terrain/chunks/planetChunk.ts index c62a1e72e..83634b677 100644 --- a/src/ts/planemos/telluricPlanemo/terrain/chunks/planetChunk.ts +++ b/src/ts/planemos/telluricPlanemo/terrain/chunks/planetChunk.ts @@ -7,18 +7,18 @@ import { Scene } from "@babylonjs/core/scene"; import "@babylonjs/core/Engines/Extensions/engine.query"; import { TransformNode, VertexData } from "@babylonjs/core/Meshes"; import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; -import { PhysicsShape, PhysicsShapeMesh } from "@babylonjs/core/Physics/v2/physicsShape"; import { Observable } from "@babylonjs/core/Misc/observable"; import { Transformable } from "../../../../uberCore/transforms/basicTransform"; import { ThinInstancePatch } from "../instancePatch/thinInstancePatch"; import { randomDownSample } from "../instancePatch/matrixBuffer"; import { Assets } from "../../../../assets"; -import { CollisionMask } from "../../../../settings"; import { isSizeOnScreenEnough } from "../../../../utils/isObjectVisibleOnScreen"; import { Camera } from "@babylonjs/core/Cameras/camera"; import { IPatch } from "../instancePatch/iPatch"; import { TelluricPlanemoModel } from "../../telluricPlanemoModel"; import { BoundingSphere } from "../../../../bodies/common"; +import { PhysicsMotionType, PhysicsShapeType } from "@babylonjs/core/Physics/v2/IPhysicsEnginePlugin"; +import { LockConstraint } from "@babylonjs/core/Physics/v2/physicsConstraint"; import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; export class PlanetChunk implements Transformable, BoundingSphere { @@ -38,12 +38,10 @@ export class PlanetChunk implements Transformable, BoundingSphere { readonly instancePatches: IPatch[] = []; - readonly onDestroyPhysicsShapeObservable = new Observable(); - readonly onRecieveVertexDataObservable = new Observable(); + readonly onDisposeObservable = new Observable(); - private physicsShape: PhysicsShape | null = null; - physicsShapeIndex: number | null = null; + aggregate: PhysicsAggregate | null = null; readonly parentAggregate: PhysicsAggregate; private averageHeight = 0; @@ -123,11 +121,13 @@ export class PlanetChunk implements Transformable, BoundingSphere { this.mesh.freezeNormals(); if (this.depth > 3) { - this.physicsShape = new PhysicsShapeMesh(this.mesh, this.mesh.getScene()); - this.physicsShape.filterMembershipMask = CollisionMask.GROUND; - this.parentAggregate.shape.addChildFromParent(this.parent, this.physicsShape, this.mesh); - this.physicsShapeIndex = this.parentAggregate.shape.getNumChildren(); + this.aggregate = new PhysicsAggregate(this.mesh, PhysicsShapeType.MESH, { mass: 0 }, this.mesh.getScene()); + this.aggregate.body.setMotionType(PhysicsMotionType.STATIC); + this.aggregate.body.disablePreStep = false; + const constraint = new LockConstraint(Vector3.Zero(), this.getTransform().position.negate(), new Vector3(0, 1, 0), new Vector3(0, 1, 0), this.mesh.getScene()); + this.parentAggregate.body.addConstraint(this.aggregate.body, constraint); } + this.mesh.setEnabled(true); this.loaded = true; @@ -160,29 +160,6 @@ export class PlanetChunk implements Transformable, BoundingSphere { return this.averageHeight; } - private destroyPhysicsShape() { - if (this.physicsShapeIndex === null) return; - if (this.physicsShapeIndex > this.parentAggregate.shape.getNumChildren() - 1) { - console.error( - `Tried to delete ${this.mesh.name} PhysicsShape. However its shape index was out of bound: ${this.physicsShapeIndex} / range 0 : ${ - this.parentAggregate.shape.getNumChildren() - 1 - }` - ); - } - - this.parentAggregate.shape.removeChild(this.physicsShapeIndex); - this.physicsShape?.dispose(); - - this.onDestroyPhysicsShapeObservable.notifyObservers(this.physicsShapeIndex); - } - - public registerPhysicsShapeDeletion(shapeIndex: number) { - if (this.physicsShapeIndex === null) return; - if (this.physicsShapeIndex > shapeIndex) { - this.physicsShapeIndex--; - } - } - public getBoundingRadius(): number { return this.chunkSideLength / 2; } @@ -200,12 +177,15 @@ export class PlanetChunk implements Transformable, BoundingSphere { } public dispose() { - this.destroyPhysicsShape(); + this.onDisposeObservable.notifyObservers(); + + this.aggregate?.dispose(); this.helpers.forEach((helper) => helper.dispose()); this.instancePatches.forEach((patch) => patch.dispose()); this.mesh.dispose(); this.transform.dispose(); this.onRecieveVertexDataObservable.clear(); + this.onDisposeObservable.clear(); this.disposed = true; } diff --git a/src/ts/playground.ts b/src/ts/playground.ts index 13115f8cc..05536fc0e 100644 --- a/src/ts/playground.ts +++ b/src/ts/playground.ts @@ -12,15 +12,15 @@ import HavokPhysics from "@babylonjs/havok"; import { HavokPlugin } from "@babylonjs/core/Physics/v2/Plugins/havokPlugin"; import { setMaxLinVel } from "./utils/havok"; import { TelluricPlanemo } from "./planemos/telluricPlanemo/telluricPlanemo"; -import { ChunkForge } from "./planemos/telluricPlanemo/terrain/chunks/chunkForge"; +import { ChunkForgeWorkers } from "./planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers"; import { StarfieldPostProcess } from "./postProcesses/starfieldPostProcess"; import { Quaternion } from "@babylonjs/core/Maths/math"; -import { FlatCloudsPostProcess } from "./postProcesses/clouds/flatCloudsPostProcess"; import { AtmosphericScatteringPostProcess } from "./postProcesses/atmosphericScatteringPostProcess"; import { Star } from "./stellarObjects/star/star"; import { LensFlarePostProcess } from "./postProcesses/lensFlarePostProcess"; import { Settings } from "./settings"; import { ScenePerformancePriority } from "@babylonjs/core"; +import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight"; const canvas = document.getElementById("renderer") as HTMLCanvasElement; canvas.width = window.innerWidth; @@ -53,6 +53,7 @@ camera.attachControl(canvas, true); const planet = new TelluricPlanemo("xrPlanet", scene, 0.51, undefined); translate(planet.getTransform(), new Vector3(0, 0, sphereRadius * 4)); +const hemiLight = new HemisphericLight("hemiLight", new Vector3(0, 1, 0), scene); const star = new Star("star", scene, 0.2); //PointLightWrapper(new PointLight("dir01", new Vector3(0, 1, 0), scene)); translate(star.getTransform(), new Vector3(0, 0, -sphereRadius * 5000)); @@ -68,7 +69,7 @@ camera.attachPostProcess(atmosphere); const lensflare = new LensFlarePostProcess(star, scene); camera.attachPostProcess(lensflare); -const chunkForge = new ChunkForge(Settings.VERTEX_RESOLUTION); +const chunkForge = new ChunkForgeWorkers(Settings.VERTEX_RESOLUTION); scene.onBeforeRenderObservable.add(() => { const deltaTime = scene.deltaTime / 1000; diff --git a/src/ts/starSystem/StarSystemView.ts b/src/ts/starSystem/StarSystemView.ts index dc8190eaf..77b9bbc2f 100644 --- a/src/ts/starSystem/StarSystemView.ts +++ b/src/ts/starSystem/StarSystemView.ts @@ -18,8 +18,9 @@ import { positionNearObject } from "../utils/positionNearObject"; import { ShipControls } from "../spaceship/shipControls"; import { OrbitRenderer } from "../orbit/orbitRenderer"; import { BlackHole } from "../stellarObjects/blackHole/blackHole"; -import { ChunkForge } from "../planemos/telluricPlanemo/terrain/chunks/chunkForge"; +import { ChunkForgeWorkers } from "../planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers"; import "@babylonjs/core/Loading/loadingScreen"; +import { ChunkForge } from "../planemos/telluricPlanemo/terrain/chunks/chunkForge"; export class StarSystemView { private readonly helmetOverlay: HelmetOverlay; @@ -29,13 +30,13 @@ export class StarSystemView { private readonly orbitRenderer: OrbitRenderer = new OrbitRenderer(); private readonly axisRenderer: AxisRenderer = new AxisRenderer(); - private readonly ui: SystemUI; + readonly ui: SystemUI; private static readonly unZoomAnimation = new Animation("unZoom", "radius", 60, Animation.ANIMATIONTYPE_FLOAT, Animation.ANIMATIONLOOPMODE_CYCLE); private starSystem: StarSystemController | null = null; - private readonly chunkForge = new ChunkForge(Settings.VERTEX_RESOLUTION); + private readonly chunkForge: ChunkForge = new ChunkForgeWorkers(Settings.VERTEX_RESOLUTION); constructor(engine: Engine, havokPlugin: HavokPlugin) { this.helmetOverlay = new HelmetOverlay(); diff --git a/src/ts/starSystem/starSystemController.ts b/src/ts/starSystem/starSystemController.ts index dabee3bc8..cefb28bc4 100644 --- a/src/ts/starSystem/starSystemController.ts +++ b/src/ts/starSystem/starSystemController.ts @@ -19,8 +19,8 @@ import { rotateAround, setUpVector, translate } from "../uberCore/transforms/bas import { Star } from "../stellarObjects/star/star"; import { BlackHole } from "../stellarObjects/blackHole/blackHole"; import { NeutronStar } from "../stellarObjects/neutronStar/neutronStar"; -import { ChunkForge } from "../planemos/telluricPlanemo/terrain/chunks/chunkForge"; import { SystemSeed } from "../utils/systemSeed"; +import { ChunkForge } from "../planemos/telluricPlanemo/terrain/chunks/chunkForge"; export class StarSystemController { readonly scene: UberScene; diff --git a/src/ts/ui/objectOverlay.ts b/src/ts/ui/objectOverlay.ts index 355d27376..4af9ec36f 100644 --- a/src/ts/ui/objectOverlay.ts +++ b/src/ts/ui/objectOverlay.ts @@ -3,7 +3,7 @@ import { AbstractObject } from "../bodies/abstractObject"; import { StackPanel } from "@babylonjs/gui/2D/controls/stackPanel"; import { Image } from "@babylonjs/gui/2D/controls/image"; import cursorImage from "../../asset/textures/hoveredCircle.png"; -import { parseDistance } from "../utils/parseToStrings"; +import { parseDistance, parseSeconds } from "../utils/parseToStrings"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { getAngularSize } from "../utils/isObjectVisibleOnScreen"; import { Camera } from "@babylonjs/core/Cameras/camera"; @@ -15,14 +15,17 @@ export class ObjectOverlay { readonly namePlate: TextBlock; readonly typeText: TextBlock; readonly distanceText: TextBlock; + readonly etaText: TextBlock; readonly object: AbstractObject; + private lastDistance: number = 0; + constructor(object: AbstractObject) { this.object = object; this.textRoot = new StackPanel(object.name + "OverlayTextRoot"); this.textRoot.width = "150px"; - this.textRoot.height = "90px"; + this.textRoot.height = "130px"; this.textRoot.background = "transparent"; this.textRoot.zIndex = 6; this.textRoot.alpha = 0.95; @@ -56,6 +59,15 @@ export class ObjectOverlay { this.distanceText.textVerticalAlignment = TextBlock.VERTICAL_ALIGNMENT_CENTER; this.textRoot.addControl(this.distanceText); + this.etaText = new TextBlock(object.name + "OverlayEtaText"); + this.etaText.color = "white"; + this.etaText.zIndex = 6; + this.etaText.height = "20px"; + this.etaText.fontSize = 16; + this.etaText.textHorizontalAlignment = TextBlock.HORIZONTAL_ALIGNMENT_LEFT; + this.etaText.textVerticalAlignment = TextBlock.VERTICAL_ALIGNMENT_CENTER; + this.textRoot.addControl(this.etaText); + this.cursor = new Image(object.name + "Cursor", cursorImage); this.cursor.fixedRatio = 1; this.cursor.width = 1; @@ -72,6 +84,8 @@ export class ObjectOverlay { const viewRay = camera.getDirection(LOCAL_DIRECTION.BACKWARD); const objectRay = this.object.getTransform().getAbsolutePosition().subtract(camera.globalPosition); const distance = objectRay.length(); + const deltaDistance = this.lastDistance - distance; + const speed = deltaDistance != 0 ? deltaDistance / (camera.getScene().getEngine().getDeltaTime() / 1000) : 0; objectRay.scaleInPlace(1 / distance); if (Vector3.Dot(viewRay, objectRay) < 0) { @@ -99,6 +113,11 @@ export class ObjectOverlay { this.textRoot.linkOffsetXInPixels = 0.5 * Math.max(scale, screenRatio) * window.innerWidth + 75 + 20; this.distanceText.text = parseDistance(distance); + + const nbSeconds = distance / speed; + this.etaText.text = speed > 0 && nbSeconds < 60 * 60 * 24 ? parseSeconds(nbSeconds) : "∞"; + + this.lastDistance = distance; } dispose() { diff --git a/src/ts/ui/systemUI.ts b/src/ts/ui/systemUI.ts index fb9a39f09..e6d6f3d28 100644 --- a/src/ts/ui/systemUI.ts +++ b/src/ts/ui/systemUI.ts @@ -5,7 +5,7 @@ import { ObjectOverlay } from "./objectOverlay"; import { Camera } from "@babylonjs/core/Cameras/camera"; export class SystemUI { - private readonly gui: AdvancedDynamicTexture; + readonly gui: AdvancedDynamicTexture; private objectOverlays: ObjectOverlay[] = []; private target: AbstractObject | null = null; diff --git a/src/ts/utils/parseToStrings.ts b/src/ts/utils/parseToStrings.ts index 5e4bc9788..bad775bd9 100644 --- a/src/ts/utils/parseToStrings.ts +++ b/src/ts/utils/parseToStrings.ts @@ -24,6 +24,18 @@ export function parseDistance(distance: number): string { } } +export function parseSeconds(seconds: number): string { + if (seconds < 60) { + return `${seconds.toFixed(0)} s`; + } else if (seconds < 3600) { + return `${(seconds / 60).toFixed(0)} min`; + } else if (seconds < 86400) { + return `${(seconds / 3600).toFixed(0)} h`; + } else { + return `${(seconds / 86400).toFixed(0)} d`; + } +} + /** * Parse a number between 0 and 1 to a percentage string. * Example: 0.5 -> "50%" diff --git a/src/ts/xr.ts b/src/ts/xr.ts index f6d5225ac..e04b6f1df 100644 --- a/src/ts/xr.ts +++ b/src/ts/xr.ts @@ -2,7 +2,6 @@ import "../styles/index.scss"; import { Assets } from "./assets"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { OceanPostProcess } from "./postProcesses/oceanPostProcess"; import { UberScene } from "./uberCore/uberScene"; import { translate } from "./uberCore/transforms/basicTransform"; import { FreeCamera } from "@babylonjs/core/Cameras/freeCamera"; @@ -12,13 +11,8 @@ import HavokPhysics from "@babylonjs/havok"; import { HavokPlugin } from "@babylonjs/core/Physics/v2/Plugins/havokPlugin"; import { setMaxLinVel } from "./utils/havok"; import { TelluricPlanemo } from "./planemos/telluricPlanemo/telluricPlanemo"; -import { ChunkForge } from "./planemos/telluricPlanemo/terrain/chunks/chunkForge"; -import { StarfieldPostProcess } from "./postProcesses/starfieldPostProcess"; -import { Quaternion } from "@babylonjs/core/Maths/math"; -import { FlatCloudsPostProcess } from "./postProcesses/clouds/flatCloudsPostProcess"; -import { AtmosphericScatteringPostProcess } from "./postProcesses/atmosphericScatteringPostProcess"; +import { ChunkForgeWorkers } from "./planemos/telluricPlanemo/terrain/chunks/chunkForgeWorkers"; import { Star } from "./stellarObjects/star/star"; -import { LensFlarePostProcess } from "./postProcesses/lensFlarePostProcess"; import { Settings } from "./settings"; import { ScenePerformancePriority } from "@babylonjs/core"; @@ -114,7 +108,7 @@ FlatCloudsPostProcess.CreateAsync("clouds", planet, planet.model.cloudsUniforms, xrCamera.attachPostProcess(lensflare); });*/ -const chunkForge = new ChunkForge(Settings.VERTEX_RESOLUTION); +const chunkForge = new ChunkForgeWorkers(Settings.VERTEX_RESOLUTION); scene.onBeforeRenderObservable.add(() => { const deltaTime = engine.getDeltaTime() / 1000;