diff --git a/package.json b/package.json index d954635d0..19158d140 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "webpack-cli": "^5.1.4", "webpack-dev-server": "^4.15.1" }, - "version": "1.4.1", + "version": "1.4.2", "description": "CosmosJourneyer", "name": "cosmos-journeyer", "scripts": { diff --git a/src/asset/spacestation/shipcarrier.glb b/src/asset/spacestation/shipcarrier.glb index abf56d954..de92336a1 100644 Binary files a/src/asset/spacestation/shipcarrier.glb and b/src/asset/spacestation/shipcarrier.glb differ diff --git a/src/ts/alphaTestis.ts b/src/ts/alphaTestis.ts index 72b60b0e4..daa0689c3 100644 --- a/src/ts/alphaTestis.ts +++ b/src/ts/alphaTestis.ts @@ -34,6 +34,8 @@ import { StarModel } from "./stellarObjects/star/starModel"; import { RingsUniforms } from "./postProcesses/rings/ringsUniform"; import { getMoonSeed } from "./planets/common"; import { SystemSeed } from "./utils/systemSeed"; +import { SpaceStation } from "./spacestation/spaceStation"; +import { PhysicsViewer } from "@babylonjs/core/Debug/physicsViewer"; const engine = await CosmosJourneyer.CreateAsync(); @@ -80,8 +82,13 @@ 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); +const spacestation = new SpaceStation(starSystemView.scene, planet); +starSystemView.getStarSystem().addSpaceStation(spacestation); + +//physicsViewer.showBody(spacestation.aggregate.body); +/*for(const landingpad of spacestation.landingPads) { + physicsViewer.showBody(landingpad.aggregate.body); +}*/ const moonModel = new TelluricPlanetModel(getMoonSeed(planetModel, 0), planetModel); moonModel.physicalProperties.mass = 2; @@ -160,6 +167,13 @@ if (aresAtmosphere) { document.addEventListener("keydown", (e) => { if (engine.isPaused()) return; + if(e.key === "o") { + const landingPad = spacestation.handleDockingRequest(); + if(landingPad !== null && starSystemView.scene.getActiveController() === spaceshipController) { + spaceshipController.spaceship.engageLandingOnPad(landingPad); + } + } + if (e.key === "x") { let nbVertices = 0; let nbInstances = 0; diff --git a/src/ts/architecture/celestialBody.ts b/src/ts/architecture/celestialBody.ts index adcc90839..b59b63f3d 100644 --- a/src/ts/architecture/celestialBody.ts +++ b/src/ts/architecture/celestialBody.ts @@ -48,4 +48,6 @@ export interface CelestialBodyModel extends OrbitalObjectModel { * The radius of the celestial body */ readonly radius: number; + + getNbSpaceStations(): number; } diff --git a/src/ts/architecture/orbitalObject.ts b/src/ts/architecture/orbitalObject.ts index 3dc064630..bf12f8983 100644 --- a/src/ts/architecture/orbitalObject.ts +++ b/src/ts/architecture/orbitalObject.ts @@ -18,7 +18,6 @@ import { Transformable } from "./transformable"; import { BoundingSphere } from "./boundingSphere"; import { OrbitProperties } from "../orbit/orbitProperties"; -import { rotateVector3AroundInPlace } from "../utils/algebra"; import { Quaternion, Vector3 } from "@babylonjs/core/Maths/math"; import { getRotationQuaternion, setRotationQuaternion, translate } from "../uberCore/transforms/basicTransform"; import { OrbitalObjectPhysicalProperties } from "./physicalProperties"; @@ -82,8 +81,8 @@ export class OrbitalObject { // rotate the object around the barycenter of the orbit, around the normal to the orbital plane const dtheta = (2 * Math.PI * deltaTime) / orbit.period; - rotateVector3AroundInPlace(newPosition, barycenter, orbit.normalToPlane, dtheta); - + const rotationQuaternion = Quaternion.RotationAxis(orbit.normalToPlane, dtheta); + newPosition.applyRotationQuaternionInPlace(rotationQuaternion); newPosition.normalize().scaleInPlace(orbit.radius); // enforce orbital plane diff --git a/src/ts/assets.ts b/src/ts/assets.ts index 7ff397b63..f139b2b53 100644 --- a/src/ts/assets.ts +++ b/src/ts/assets.ts @@ -73,6 +73,7 @@ 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 { Axis } from "@babylonjs/core/Maths/math.axis"; export class Assets { static IS_READY = false; diff --git a/src/ts/landingPad/landingPad.ts b/src/ts/landingPad/landingPad.ts index 0a134071d..b3587626b 100644 --- a/src/ts/landingPad/landingPad.ts +++ b/src/ts/landingPad/landingPad.ts @@ -6,17 +6,28 @@ import { PhysicsShapeConvexHull } from "@babylonjs/core/Physics/v2/physicsShape" import { Mesh } from "@babylonjs/core/Meshes/mesh"; import { CollisionMask } from "../settings"; import { Scene } from "@babylonjs/core/scene"; -import { Vector3 } from "@babylonjs/core/Maths/math"; +import { Quaternion, Vector3 } from "@babylonjs/core/Maths/math"; import { Transformable } from "../architecture/transformable"; +import { AbstractMesh } from "@babylonjs/core/Meshes/abstractMesh"; +import { PhysicsMotionType } from "@babylonjs/core"; +import { Axis } from "@babylonjs/core/Maths/math.axis"; export class LandingPad implements Transformable { readonly instanceRoot: TransformNode; - readonly aggregate: PhysicsAggregate; - constructor(scene: Scene) { - this.instanceRoot = Assets.CreateLandingPadInstance(); + //readonly aggregate: PhysicsAggregate; - this.aggregate = new PhysicsAggregate( + constructor(scene: Scene, existingMesh: AbstractMesh | null = null) { + if (existingMesh === null) { + this.instanceRoot = Assets.CreateLandingPadInstance(); + } else { + this.instanceRoot = existingMesh; + } + + // init rotation quaternion + this.instanceRoot.rotate(Axis.X, 0); + + /*this.aggregate = new PhysicsAggregate( this.instanceRoot, PhysicsShapeType.CONTAINER, { @@ -26,6 +37,8 @@ export class LandingPad implements Transformable { scene ); + this.aggregate.body.setMotionType(PhysicsMotionType.STATIC); + this.aggregate.body.setMassProperties({ inertia: Vector3.Zero(), mass: 0 }); for (const child of this.instanceRoot.getChildMeshes()) { @@ -33,15 +46,15 @@ export class LandingPad implements Transformable { childShape.filterMembershipMask = CollisionMask.LANDING_PADS; this.aggregate.shape.addChildFromParent(this.instanceRoot, childShape, child); } - this.aggregate.body.disablePreStep = false; + this.aggregate.body.disablePreStep = false;*/ } getTransform(): TransformNode { - return this.aggregate.transformNode; + return this.instanceRoot; } dispose() { - this.aggregate.dispose(); + //this.aggregate.dispose(); this.instanceRoot.dispose(); } } diff --git a/src/ts/landingSimulator.ts b/src/ts/landingSimulator.ts index ad1f43a40..2ede3f364 100644 --- a/src/ts/landingSimulator.ts +++ b/src/ts/landingSimulator.ts @@ -7,7 +7,7 @@ import { setMaxLinVel } from "./utils/havok"; import { UberScene } from "./uberCore/uberScene"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { Assets } from "./assets"; -import { roll, translate } from "./uberCore/transforms/basicTransform"; +import { roll, setRotationQuaternion, translate } from "./uberCore/transforms/basicTransform"; import { DirectionalLight } from "@babylonjs/core/Lights/directionalLight"; import { Color4 } from "@babylonjs/core/Maths/math.color"; import "@babylonjs/core/Physics/physicsEngineComponent"; @@ -21,6 +21,10 @@ import { ShadowGenerator } from "@babylonjs/core/Lights/Shadows/shadowGenerator" import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder"; import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight"; import { PointLight } from "@babylonjs/core/Lights/pointLight"; +import { SpaceStation } from "./spacestation/spaceStation"; +import { Quaternion } from "@babylonjs/core/Maths/math"; +import { Axis } from "@babylonjs/core/Maths/math.axis"; +import { OrbitalObject } from "./architecture/orbitalObject"; const canvas = document.getElementById("renderer") as HTMLCanvasElement; canvas.width = window.innerWidth; @@ -53,19 +57,33 @@ shadowGenerator.addShadowCaster(spaceship.instanceRoot, true); spaceship.getTransform().position = new Vector3(0, 0, -10); roll(spaceship.getTransform(), Math.random() * 6.28); -const landingPad = new LandingPad(scene); +/*const landingPad = new LandingPad(scene); landingPad.getTransform().position = new Vector3(0, -20, 0); landingPad.instanceRoot.getChildMeshes().forEach((mesh) => { mesh.receiveShadows = true; -}); +});*/ + +const physicsViewer = new PhysicsViewer(); +/*physicsViewer.showBody(spaceship.aggregate.body); +physicsViewer.showBody(landingPad.aggregate.body);*/ + +const spacestation = new SpaceStation(scene); +setRotationQuaternion(spacestation.getTransform(), Quaternion.RotationAxis(Axis.X, Math.PI / 2)); +translate(spacestation.getTransform(), new Vector3(0, -100, 0)); + +//physicsViewer.showBody(spacestation.aggregate.body); +/*spacestation.landingPads.forEach(stationLandingPad => { + physicsViewer.showBody(stationLandingPad.aggregate.body); +});*/ + +/*spacestation.ringAggregates.forEach(ring => { + physicsViewer.showBody(ring.body); +});*/ /*const ground = MeshBuilder.CreateGround("ground", { width: 50, height: 50 }); ground.position.y = -40; ground.receiveShadows = true;*/ -/*const physicsViewer = new PhysicsViewer(); -physicsViewer.showBody(spaceship.aggregate.body); -physicsViewer.showBody(landingPad.aggregate.body);*/ const defaultControls = new DefaultControls(scene); defaultControls.speed *= 15; @@ -74,12 +92,18 @@ scene.setActiveController(defaultControls); translate(defaultControls.getTransform(), new Vector3(50, 0, 0)); -defaultControls.getTransform().lookAt(Vector3.Lerp(spaceship.getTransform().position, landingPad.getTransform().position, 0.5)); +//defaultControls.getTransform().lookAt(Vector3.Lerp(spaceship.getTransform().position, landingPad.getTransform().position, 0.5)); scene.onBeforeRenderObservable.add(() => { const deltaTime = scene.deltaTime / 1000; scene.getActiveController().update(deltaTime); spaceship.update(deltaTime); + + //OrbitalObject.UpdateRotation(spacestation, deltaTime); + + spacestation.ringInstances.forEach(mesh => { + mesh.rotate(Axis.Y, 0.01 * deltaTime); + }); }); scene.executeWhenReady(() => { @@ -88,9 +112,12 @@ scene.executeWhenReady(() => { }); }); +const landingPad = spacestation.handleDockingRequest(); +if(landingPad === null) throw new Error("Docking request denied"); + document.addEventListener("keydown", (event) => { if (event.key === "o") { - spaceship.engageLanding(landingPad); + spaceship.engageLandingOnPad(landingPad); } }); diff --git a/src/ts/mandelbulb/mandelbulbModel.ts b/src/ts/mandelbulb/mandelbulbModel.ts index 9c778f5f3..1e8970a2c 100644 --- a/src/ts/mandelbulb/mandelbulbModel.ts +++ b/src/ts/mandelbulb/mandelbulbModel.ts @@ -90,4 +90,8 @@ export class MandelbulbModel implements PlanetModel { getApparentRadius(): number { return this.radius; } + + getNbSpaceStations(): number { + return 0; + } } diff --git a/src/ts/model/common.ts b/src/ts/model/common.ts index 5c27377c0..90ca98638 100644 --- a/src/ts/model/common.ts +++ b/src/ts/model/common.ts @@ -35,7 +35,9 @@ export enum GENERATION_STEPS { PRESSURE = 1100, WATER_AMOUNT = 1200, - TERRAIN = 1500 + TERRAIN = 1500, + + SPACE_STATION = 2000 } export enum BODY_TYPE { diff --git a/src/ts/planets/gasPlanet/gasPlanetModel.ts b/src/ts/planets/gasPlanet/gasPlanetModel.ts index fa9285f2a..d5fdad5ca 100644 --- a/src/ts/planets/gasPlanet/gasPlanetModel.ts +++ b/src/ts/planets/gasPlanet/gasPlanetModel.ts @@ -97,4 +97,10 @@ export class GasPlanetModel implements PlanetModel { getApparentRadius(): number { return this.radius; } + + public getNbSpaceStations(): number { + if(uniformRandBool(0.2, this.rng, GENERATION_STEPS.SPACE_STATION)) return 1; + if(uniformRandBool(0.1, this.rng, GENERATION_STEPS.SPACE_STATION + 10)) return 2; + return 0; + } } diff --git a/src/ts/planets/telluricPlanet/telluricPlanetModel.ts b/src/ts/planets/telluricPlanet/telluricPlanetModel.ts index f6bd3fcae..78e8485cf 100644 --- a/src/ts/planets/telluricPlanet/telluricPlanetModel.ts +++ b/src/ts/planets/telluricPlanet/telluricPlanetModel.ts @@ -153,4 +153,10 @@ export class TelluricPlanetModel implements PlanetModel { getApparentRadius(): number { return this.radius + this.physicalProperties.oceanLevel; } + + public getNbSpaceStations(): number { + if(uniformRandBool(0.2, this.rng, GENERATION_STEPS.SPACE_STATION)) return 1; + if(uniformRandBool(0.1, this.rng, GENERATION_STEPS.SPACE_STATION + 10)) return 2; + return 0; + } } diff --git a/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts b/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts index 1d9b0e311..3492d434f 100644 --- a/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts +++ b/src/ts/planets/telluricPlanet/terrain/chunks/planetChunk.ts @@ -84,8 +84,8 @@ export class PlanetChunk implements Transformable, BoundingSphere { this.mesh.parent = parentAggregate.transformNode; - this.mesh.occlusionQueryAlgorithmType = AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE; - this.mesh.occlusionType = AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC; + //this.mesh.occlusionQueryAlgorithmType = AbstractMesh.OCCLUSION_ALGORITHM_TYPE_CONSERVATIVE; + //this.mesh.occlusionType = AbstractMesh.OCCLUSION_TYPE_OPTIMISTIC; this.parent = parentAggregate.transformNode; this.parentAggregate = parentAggregate; @@ -138,7 +138,8 @@ export class PlanetChunk implements Transformable, BoundingSphere { this.aggregate = new PhysicsAggregate(this.mesh, PhysicsShapeType.MESH, { mass: 0 }, this.mesh.getScene()); this.aggregate.body.setMotionType(PhysicsMotionType.STATIC); this.aggregate.body.disablePreStep = false; - this.aggregate.shape.filterMembershipMask = CollisionMask.GROUND; + this.aggregate.shape.filterMembershipMask = CollisionMask.ENVIRONMENT; + this.aggregate.shape.filterCollideMask = CollisionMask.DYNAMIC_OBJECTS; 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); } diff --git a/src/ts/settings.ts b/src/ts/settings.ts index 42b854b29..90e4243c0 100644 --- a/src/ts/settings.ts +++ b/src/ts/settings.ts @@ -41,9 +41,8 @@ export const Settings = { }; export const CollisionMask = { - GROUND: 0b00000001, - SPACESHIP: 0b00000010, - LANDING_PADS: 0b00000100 + ENVIRONMENT: 0b00000001, + DYNAMIC_OBJECTS: 0b00000010 }; const seedableRNG = seededSquirrelNoise(Settings.UNIVERSE_SEED); diff --git a/src/ts/spacelegs/characterControls.ts b/src/ts/spacelegs/characterControls.ts index 27e5075c6..71d095bf5 100644 --- a/src/ts/spacelegs/characterControls.ts +++ b/src/ts/spacelegs/characterControls.ts @@ -249,7 +249,7 @@ export class CharacterControls implements Controls { setUpVector(character, up); } - (this.scene.getPhysicsEngine() as PhysicsEngineV2).raycastToRef(start, end, this.raycastResult, { collideWith: CollisionMask.GROUND }); + (this.scene.getPhysicsEngine() as PhysicsEngineV2).raycastToRef(start, end, this.raycastResult, { collideWith: CollisionMask.ENVIRONMENT }); if (this.raycastResult.hasHit) { const up = character.up; const distance = Vector3.Dot(character.getAbsolutePosition().subtract(this.raycastResult.hitPointWorld), up); diff --git a/src/ts/spaceship/shipControls.ts b/src/ts/spaceship/shipControls.ts index 4af98deab..70bd29212 100644 --- a/src/ts/spaceship/shipControls.ts +++ b/src/ts/spaceship/shipControls.ts @@ -136,10 +136,6 @@ export class ShipControls implements Controls { const deltaThrottle = keyboard.getZAxis() * deltaTime; this.spaceship.getWarpDrive().increaseTargetThrottle(deltaThrottle); } - - const warpSpeed = getForwardDirection(this.getTransform()).scale(this.spaceship.getWarpDrive().getWarpSpeed()); - //this.aggregate.body.setLinearVelocity(warpSpeed); - translate(this.getTransform(), warpSpeed.scale(deltaTime)); } } diff --git a/src/ts/spaceship/spaceship.ts b/src/ts/spaceship/spaceship.ts index 89d256e99..e08dc9472 100644 --- a/src/ts/spaceship/spaceship.ts +++ b/src/ts/spaceship/spaceship.ts @@ -30,20 +30,20 @@ import { Axis } from "@babylonjs/core/Maths/math.axis"; import { HavokPlugin } from "@babylonjs/core/Physics/v2/Plugins/havokPlugin"; import { setEnabledBody } from "../utils/havok"; import { - getForwardDirection, - getUpwardDirection, - rotate, + getForwardDirection, getRotationQuaternion, getUpwardDirection, rotate, setRotationQuaternion, translate } from "../uberCore/transforms/basicTransform"; import { TransformNode } from "@babylonjs/core/Meshes"; import { Assets } from "../assets"; import { PhysicsRaycastResult } from "@babylonjs/core/Physics/physicsRaycastResult"; -import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2"; import { CollisionMask } from "../settings"; import { Transformable } from "../architecture/transformable"; import { WarpTunnel } from "../utils/warpTunnel"; import { Quaternion } from "@babylonjs/core/Maths/math"; +import { MeshBuilder } from "@babylonjs/core/Meshes/meshBuilder"; +import { LandingPad } from "../landingPad/landingPad"; +import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2"; enum ShipState { FLYING, @@ -80,6 +80,8 @@ export class Spaceship implements Transformable { private readonly scene: Scene; + private targetLandingPad: LandingPad | null = null; + constructor(scene: Scene) { this.instanceRoot = Assets.CreateSpaceShipInstance(); setRotationQuaternion(this.instanceRoot, Quaternion.Identity()); @@ -95,7 +97,8 @@ export class Spaceship implements Transformable { ); for (const child of this.instanceRoot.getChildMeshes()) { const childShape = new PhysicsShapeMesh(child as Mesh, scene); - childShape.filterMembershipMask = CollisionMask.SPACESHIP; + childShape.filterMembershipMask = CollisionMask.DYNAMIC_OBJECTS; + childShape.filterCollideMask = CollisionMask.ENVIRONMENT; this.aggregate.shape.addChildFromParent(this.instanceRoot, childShape, child); } this.aggregate.body.disablePreStep = false; @@ -218,6 +221,13 @@ export class Spaceship implements Transformable { console.log("landing on", this.landingTarget.getTransform().name); } + public engageLandingOnPad(landingPad: LandingPad) { + console.log("Landing on pad", landingPad.getTransform().name); + this.aggregate.body.setMotionType(PhysicsMotionType.ANIMATED); + this.state = ShipState.LANDING; + this.targetLandingPad = landingPad; + } + private completeLanding() { console.log("Landing sequence complete"); this.state = ShipState.LANDED; @@ -225,45 +235,17 @@ export class Spaceship implements Transformable { this.landingTarget = null; } - public update(deltaTime: number) { - const warpSpeed = getForwardDirection(this.aggregate.transformNode).scale(this.warpDrive.getWarpSpeed()); - - const currentForwardSpeed = Vector3.Dot(warpSpeed, this.aggregate.transformNode.getDirection(Axis.Z)); - this.warpDrive.update(currentForwardSpeed, this.closestObject.distance, this.closestObject.radius, deltaTime); - - // the warp throttle goes from 0.1 to 1 smoothly using an inverse function - if (this.warpDrive.isEnabled()) this.warpTunnel.setThrottle(1 - 1 / (1.1 * (1 + 1e-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(); - - if (this.closestWalkableObject !== null) { - const gravityDir = this.closestWalkableObject.getTransform().getAbsolutePosition().subtract(this.getTransform().getAbsolutePosition()).normalize(); - this.aggregate.body.applyForce(gravityDir.scale(9.8), this.aggregate.body.getObjectCenterWorld()); - } - } - - if (this.flightAssistEnabled) { - this.aggregate.body.setAngularDamping(0.9); - } else { - this.aggregate.body.setAngularDamping(1); + private land(deltaTime: number) { + if(this.targetLandingPad !== null) { + this.landOnPad(this.targetLandingPad, deltaTime); } - if (this.state === ShipState.LANDING) { - if (this.landingTarget === null) { - throw new Error("Closest walkable object is null while landing"); - } - + if(this.landingTarget !== null) { const gravityDir = this.landingTarget.getTransform().getAbsolutePosition().subtract(this.getTransform().getAbsolutePosition()).normalize(); const start = this.getTransform().getAbsolutePosition().add(gravityDir.scale(-50e3)); const end = this.getTransform().getAbsolutePosition().add(gravityDir.scale(50e3)); - (this.scene.getPhysicsEngine() as PhysicsEngineV2).raycastToRef(start, end, this.raycastResult, { collideWith: CollisionMask.GROUND | CollisionMask.LANDING_PADS }); + (this.scene.getPhysicsEngine() as PhysicsEngineV2).raycastToRef(start, end, this.raycastResult, { collideWith: CollisionMask.ENVIRONMENT }); if (this.raycastResult.hasHit) { const landingSpotNormal = this.raycastResult.hitNormalWorld; const extent = this.instanceRoot.getHierarchyBoundingVectors(); @@ -291,6 +273,64 @@ export class Spaceship implements Transformable { } } + private landOnPad(landingPad: LandingPad, deltaTime: number) { + const padUp = landingPad.getTransform().up; + + const targetPosition = landingPad.getTransform().getAbsolutePosition(); + targetPosition.addInPlace(padUp.scale(2)); + const currentPosition = this.getTransform().getAbsolutePosition(); + + const distance = Vector3.Distance(targetPosition, currentPosition); + + if(distance < 0.01) { + this.completeLanding(); + return; + } + + const targetOrientation = landingPad.getTransform().absoluteRotationQuaternion; + const currentOrientation = getRotationQuaternion(this.getTransform()); + + translate(this.getTransform(), targetPosition.subtract(currentPosition).normalize().scaleInPlace(Math.min(distance, 20 * deltaTime))); + + this.getTransform().rotationQuaternion = Quaternion.Slerp(currentOrientation, targetOrientation, deltaTime); + } + + public update(deltaTime: number) { + const warpSpeed = getForwardDirection(this.aggregate.transformNode).scale(this.warpDrive.getWarpSpeed()); + + const currentForwardSpeed = Vector3.Dot(warpSpeed, this.aggregate.transformNode.getDirection(Axis.Z)); + this.warpDrive.update(currentForwardSpeed, this.closestObject.distance, this.closestObject.radius, deltaTime); + + // the warp throttle goes from 0.1 to 1 smoothly using an inverse function + if (this.warpDrive.isEnabled()) this.warpTunnel.setThrottle(1 - 1 / (1.1 * (1 + 1e-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(); + + if (this.closestWalkableObject !== null) { + const gravityDir = this.closestWalkableObject.getTransform().getAbsolutePosition().subtract(this.getTransform().getAbsolutePosition()).normalize(); + this.aggregate.body.applyForce(gravityDir.scale(9.8), this.aggregate.body.getObjectCenterWorld()); + } + } else { + translate(this.getTransform(), warpSpeed.scale(deltaTime)); + } + + if (this.flightAssistEnabled) { + this.aggregate.body.setAngularDamping(0.9); + } else { + this.aggregate.body.setAngularDamping(1); + } + + if (this.state === ShipState.LANDING) { + this.land(deltaTime); + } + } + public dispose() { this.aggregate.dispose(); this.instanceRoot.dispose(); diff --git a/src/ts/spaceship/warpDrive.ts b/src/ts/spaceship/warpDrive.ts index dc7fdcd95..b2eba3451 100644 --- a/src/ts/spaceship/warpDrive.ts +++ b/src/ts/spaceship/warpDrive.ts @@ -116,6 +116,8 @@ export class WarpDrive implements ReadonlyWarpDrive { */ private state = WARPDRIVE_STATE.DISABLED; + private static MIN_SPEED = 500; + constructor(enabledByDefault = false) { this.state = enabledByDefault ? WARPDRIVE_STATE.ENABLED : WARPDRIVE_STATE.DISABLED; } @@ -159,12 +161,13 @@ export class WarpDrive implements ReadonlyWarpDrive { /** * Computes the target speed of the warp drive based on the distance to the closest body and the user throttle. * @param closestObjectDistance The distance to the closest body in m. + * @param closestObjectRadius * @returns The computed target speed in m/s. */ public updateTargetSpeed(closestObjectDistance: number, closestObjectRadius: number): number { const speedThreshold = 10e3; - const closeSpeed = (speedThreshold * 0.025 * (closestObjectDistance - closestObjectRadius)) / speedThreshold; - const deepSpaceSpeed = speedThreshold * ((0.025 * (closestObjectDistance - closestObjectRadius)) / speedThreshold) ** 1.1; + const closeSpeed = (speedThreshold * 0.025 * Math.max(0, closestObjectDistance - closestObjectRadius)) / speedThreshold; + const deepSpaceSpeed = speedThreshold * ((0.025 * Math.max(0, closestObjectDistance - closestObjectRadius)) / speedThreshold) ** 1.1; this.targetSpeed = Math.min(this.maxWarpSpeed, Math.max(closeSpeed, deepSpaceSpeed)); return this.targetThrottle * this.targetSpeed; } @@ -208,13 +211,14 @@ export class WarpDrive implements ReadonlyWarpDrive { const deltaThrottle = this.internalThrottleAcceleration * deltaTime; this.increaseInternalThrottle(deltaThrottle * sign); - this.currentSpeed = this.internalThrottle * this.targetSpeed; + this.currentSpeed = Math.max(WarpDrive.MIN_SPEED, this.internalThrottle * this.targetSpeed); } /** * Updates the warp drive based on the current speed of the ship, the distance to the closest body and the time elapsed since the last update. * @param currentForwardSpeed The current speed of the warp drive projected on the forward direction of the ship. - * @param closestObjectPosition The distance to the closest body in m. + * @param closestObjectDistance + * @param clostestObjectRadius * @param deltaTime The time elapsed since the last update in seconds. */ public update(currentForwardSpeed: number, closestObjectDistance: number, clostestObjectRadius: number, deltaTime: number): void { @@ -222,7 +226,7 @@ export class WarpDrive implements ReadonlyWarpDrive { case WARPDRIVE_STATE.DESENGAGING: this.targetSpeed *= 0.9; this.updateWarpDriveSpeed(currentForwardSpeed, deltaTime); - if (this.targetSpeed < 1e2 && this.currentSpeed < 1e2) this.disable(); + if (this.targetSpeed <= WarpDrive.MIN_SPEED && this.currentSpeed <= WarpDrive.MIN_SPEED) this.disable(); break; case WARPDRIVE_STATE.ENABLED: this.updateTargetSpeed(closestObjectDistance, clostestObjectRadius); diff --git a/src/ts/spacestation/spaceStation.ts b/src/ts/spacestation/spaceStation.ts index 31727e654..a87a7faa8 100644 --- a/src/ts/spacestation/spaceStation.ts +++ b/src/ts/spacestation/spaceStation.ts @@ -22,28 +22,40 @@ import { Camera } from "@babylonjs/core/Cameras/camera"; import { SpaceStationModel } from "./spacestationModel"; import { PostProcessType } from "../postProcesses/postProcessTypes"; import { Assets } from "../assets"; -import { Axis } from "@babylonjs/core/Maths/math.axis"; import { OrbitalObject } from "../architecture/orbitalObject"; import { Cullable } from "../bodies/cullable"; import { TransformNode } from "@babylonjs/core/Meshes"; import { OrbitProperties } from "../orbit/orbitProperties"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { OrbitalObjectPhysicalProperties } from "../architecture/physicalProperties"; +import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate"; +import { PhysicsMotionType, PhysicsShapeType } from "@babylonjs/core"; +import { LandingPad } from "../landingPad/landingPad"; +import { PhysicsShapeConvexHull, PhysicsShapeMesh } from "@babylonjs/core/Physics/v2/physicsShape"; +import { Mesh } from "@babylonjs/core/Meshes/mesh"; +import { LockConstraint } from "@babylonjs/core/Physics/v2/physicsConstraint"; +import { CollisionMask } from "../settings"; +import { CelestialBody } from "../architecture/celestialBody"; export class SpaceStation implements OrbitalObject, Cullable { readonly name: string; readonly model: SpaceStationModel; + readonly aggregate: PhysicsAggregate; + readonly postProcesses: PostProcessType[] = []; readonly instance: InstancedMesh; readonly ringInstances: InstancedMesh[] = []; + readonly ringAggregates: PhysicsAggregate[] = []; + + readonly landingPads: LandingPad[] = []; readonly parent: OrbitalObject | null = null; - constructor(scene: Scene, parentBody: OrbitalObject | null = null) { + constructor(scene: Scene, parentBody: CelestialBody | null = null) { //TODO: do not hardcode name this.name = "Spacestation"; @@ -55,16 +67,75 @@ export class SpaceStation implements OrbitalObject, Cullable { this.parent = parentBody; this.instance = Assets.CreateSpaceStationInstance(); - this.instance.parent = this.getTransform(); + + this.aggregate = new PhysicsAggregate( + this.getTransform(), + PhysicsShapeType.CONTAINER, + { + mass: 0, + restitution: 0.2 + }, + scene + ); + + this.aggregate.body.setMotionType(PhysicsMotionType.STATIC); + this.aggregate.shape.filterMembershipMask = CollisionMask.ENVIRONMENT; + this.aggregate.shape.filterCollideMask = CollisionMask.DYNAMIC_OBJECTS; + + this.aggregate.body.setCollisionCallbackEnabled(true); + this.aggregate.body.getCollisionObservable().add(() => { + console.log("collision!"); + }); + + this.aggregate.body.setMassProperties({ inertia: Vector3.Zero(), mass: 0 }); for (const mesh of this.instance.getChildMeshes()) { - if (mesh.name.includes("ring")) { + if (mesh.name.toLowerCase().includes("landingpad")) { + const childShape = new PhysicsShapeConvexHull(mesh as Mesh, scene); + childShape.filterMembershipMask = CollisionMask.ENVIRONMENT; + childShape.filterCollideMask = CollisionMask.DYNAMIC_OBJECTS; + this.aggregate.shape.addChildFromParent(this.getTransform(), childShape, mesh); + + const landingPad = new LandingPad(scene, mesh); + this.landingPads.push(landingPad); + + /*const constraint = new LockConstraint(Vector3.Zero(), landingPad.getTransform().position.negate(), new Vector3(0, 1, 0), new Vector3(0, 1, 0), scene); + this.aggregate.body.addConstraint(landingPad.aggregate.body, constraint);*/ + + continue; + } + + if (mesh.name.toLowerCase().includes("ring")) { this.ringInstances.push(mesh as InstancedMesh); + + const ringAggregate = new PhysicsAggregate(mesh, PhysicsShapeType.MESH, { mass: 0, restitution: 0.2 }, scene); + ringAggregate.body.disablePreStep = false; + this.ringAggregates.push(ringAggregate); + + const constraint = new LockConstraint(Vector3.Zero(), mesh.position.negate(), new Vector3(0, 1, 0), new Vector3(0, 1, 0), scene); + this.aggregate.body.addConstraint(ringAggregate.body, constraint); + + continue; } + + const childShape = new PhysicsShapeMesh(mesh as Mesh, scene); + childShape.filterMembershipMask = CollisionMask.ENVIRONMENT; + childShape.filterCollideMask = CollisionMask.DYNAMIC_OBJECTS; + this.aggregate.shape.addChildFromParent(this.getTransform(), childShape, mesh); } - this.getTransform().rotate(Axis.X, this.model.physicalProperties.axialTilt); - this.getTransform().rotate(Axis.Y, this.model.physicalProperties.axialTilt); + this.aggregate.body.disablePreStep = false; + + console.log("found", this.landingPads.length, "landing pads"); + } + + handleDockingRequest(): LandingPad | null { + const availableLandingPads = this.landingPads; + const nbPads = availableLandingPads.length; + + if (nbPads === 0) return null; + + return availableLandingPads[Math.floor(Math.random() * nbPads)]; } getTransform(): TransformNode { @@ -98,18 +169,6 @@ export class SpaceStation implements OrbitalObject, Cullable { } } - public updateRotation(deltaTime: number): number { - const dtheta = deltaTime / this.model.physicalProperties.rotationPeriod; - - if (this.ringInstances.length === 0) this.instance.rotate(Axis.Z, dtheta); - else { - for (const ring of this.ringInstances) { - ring.rotate(Axis.Y, dtheta); - } - } - return dtheta; - } - public dispose(): void { this.instance.dispose(); } diff --git a/src/ts/spacestation/spacestationModel.ts b/src/ts/spacestation/spacestationModel.ts index 059c29c43..4c553916c 100644 --- a/src/ts/spacestation/spacestationModel.ts +++ b/src/ts/spacestation/spacestationModel.ts @@ -16,13 +16,13 @@ // along with this program. If not, see . import { seededSquirrelNoise } from "squirrel-noise"; -import { Settings } from "../settings"; import { GENERATION_STEPS } from "../model/common"; import { OrbitProperties } from "../orbit/orbitProperties"; import { getOrbitalPeriod } from "../orbit/orbit"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; import { OrbitalObjectModel } from "../architecture/orbitalObject"; import { OrbitalObjectPhysicalProperties } from "../architecture/physicalProperties"; +import { CelestialBodyModel } from "../architecture/celestialBody"; export class SpaceStationModel implements OrbitalObjectModel { readonly seed: number; @@ -32,15 +32,14 @@ export class SpaceStationModel implements OrbitalObjectModel { readonly parentBody: OrbitalObjectModel | null; readonly childrenBodies: OrbitalObjectModel[] = []; - constructor(seed: number, parentBody?: OrbitalObjectModel) { + constructor(seed: number, parentBody?: CelestialBodyModel) { this.seed = seed; this.rng = seededSquirrelNoise(this.seed); this.parentBody = parentBody ?? null; this.childrenBodies = []; - //TODO: do not hardcode - const orbitRadius = 3 * Settings.EARTH_RADIUS; + const orbitRadius = 3 * (parentBody?.radius ?? 0); this.orbit = { radius: orbitRadius, diff --git a/src/ts/starSystem/StarSystemView.ts b/src/ts/starSystem/StarSystemView.ts index ec041eb74..f0ea3e7f2 100644 --- a/src/ts/starSystem/StarSystemView.ts +++ b/src/ts/starSystem/StarSystemView.ts @@ -131,8 +131,8 @@ export class StarSystemView { ambientLight.intensity = 0.3; this.scene.onBeforePhysicsObservable.add(() => { - const deltaTime = engine.getDeltaTime() / 1000; - this.update(deltaTime); + const deltaSeconds = engine.getDeltaTime() / 1000; + this.update(deltaSeconds * Settings.TIME_MULTIPLIER); }); window.addEventListener("resize", () => { @@ -156,8 +156,8 @@ export class StarSystemView { const firstBody = this.getStarSystem().getBodies()[0]; if (firstBody === undefined) throw new Error("No bodies in star system"); - this.orbitRenderer.setOrbitalObjects(this.getStarSystem().getBodies()); - this.axisRenderer.setObjects(this.getStarSystem().getBodies()); + this.orbitRenderer.setOrbitalObjects(this.getStarSystem().getOrbitalObjects()); + this.axisRenderer.setObjects(this.getStarSystem().getOrbitalObjects()); const activeController = this.scene.getActiveController(); let controllerDistanceFactor = 5; @@ -206,14 +206,18 @@ export class StarSystemView { this.scene.setActiveController(this.spaceshipControls); } - update(deltaTime: number) { + /** + * Updates the system view. It updates the underlying star system, the UI, the chunk forge and the controls + * @param deltaSeconds the time elapsed since the last update in seconds + */ + update(deltaSeconds: number) { const starSystem = this.getStarSystem(); - Assets.ButterflyMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaTime); - Assets.GrassMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaTime); + Assets.ButterflyMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaSeconds); + Assets.GrassMaterial.update(starSystem.stellarObjects, this.scene.getActiveController().getTransform().getAbsolutePosition(), deltaSeconds); this.chunkForge.update(); - starSystem.update(deltaTime * Settings.TIME_MULTIPLIER, this.chunkForge); + starSystem.update(deltaSeconds, this.chunkForge); if (this.spaceshipControls === null) throw new Error("Spaceship controls is null"); if (this.characterControls === null) throw new Error("Character controls is null"); @@ -225,14 +229,11 @@ export class StarSystemView { this.spaceshipControls.spaceship.registerClosestObject(distance, radius); const warpDrive = this.spaceshipControls.spaceship.getWarpDrive(); - const shipInternalThrottle = warpDrive.getInternalThrottle(); - const shipTargetThrottle = warpDrive.getTargetThrottle(); - - const throttleString = warpDrive.isEnabled() - ? `${parsePercentageFrom01(shipInternalThrottle)}/${parsePercentageFrom01(shipTargetThrottle)}` - : `${parsePercentageFrom01(this.spaceshipControls.spaceship.getThrottle())}/100%`; - - (document.querySelector("#speedometer") as HTMLElement).innerHTML = `${throttleString} | ${parseSpeed(this.spaceshipControls.spaceship.getSpeed())}`; + if(warpDrive.isEnabled()) { + this.helmetOverlay.displaySpeed(warpDrive.getInternalThrottle(), warpDrive.getTargetThrottle(), this.spaceshipControls.spaceship.getSpeed()); + } else { + this.helmetOverlay.displaySpeed(this.spaceshipControls.spaceship.getThrottle(), 100, this.spaceshipControls.spaceship.getSpeed()); + } this.characterControls.setClosestWalkableObject(nearestBody); this.spaceshipControls.spaceship.setClosestWalkableObject(nearestBody); diff --git a/src/ts/starSystem/starSystemController.ts b/src/ts/starSystem/starSystemController.ts index 04dcde9ea..422269695 100644 --- a/src/ts/starSystem/starSystemController.ts +++ b/src/ts/starSystem/starSystemController.ts @@ -187,7 +187,7 @@ export class StarSystemController { smallerDistance = -1; for (const spacestation of this.spaceStations) { - const distance = spacestation.getTransform().getAbsolutePosition().subtract(position).length() - spacestation.getBoundingRadius() * 50; + const distance = spacestation.getTransform().getAbsolutePosition().subtract(position).length() - spacestation.getBoundingRadius() * 10; if (distance < smallerDistance && distance < 0) { nearest = spacestation; smallerDistance = distance; @@ -331,75 +331,84 @@ export class StarSystemController { } /** - * Updates the system and all its bodies forward in time by the given delta time + * Updates the system and all its orbital objects forward in time by the given delta time. + * The nearest object is kept in place and the other objects are updated accordingly. * @param deltaTime The time elapsed since the last update - * @param chunkForge + * @param chunkForge The chunk forge used to update the LOD of the telluric planets */ public update(deltaTime: number, chunkForge: ChunkForge): void { const controller = this.scene.getActiveController(); this.computeNearestOrbitalObject(controller.getActiveCamera().globalPosition); this.computeClosestToScreenCenterOrbitalObject(); - const nearestBody = this.getNearestOrbitalObject(); - - const distanceOfNearestToCamera = Vector3.Distance(nearestBody.getTransform().getAbsolutePosition(), controller.getActiveCamera().globalPosition); - const shouldCompensateTranslation = distanceOfNearestToCamera < nearestBody.getBoundingRadius() * (nearestBody instanceof SpaceStation ? 80 : 10); - const shouldCompensateRotation = distanceOfNearestToCamera < nearestBody.getBoundingRadius() * 4; - //nearestBody.updateInternalClock(deltaTime); - const initialPosition = nearestBody.getTransform().getAbsolutePosition(); - const newPosition = OrbitalObject.GetNextOrbitalPosition(nearestBody, deltaTime); - const nearestBodyDisplacement = newPosition.subtract(initialPosition); - if (!shouldCompensateTranslation) translate(nearestBody.getTransform(), nearestBodyDisplacement); + // The nearest body might have to be treated separatly + // The first step is to find the nearest body + const nearestBody = this.getNearestOrbitalObject(); - const dthetaNearest = OrbitalObject.GetRotationAngle(nearestBody, deltaTime); + // Depending on the distance to the nearest body, we might have to compensate its translation and/or rotation + // If we are very close, we want both translation and rotation to be compensated, so that the body appears to be fixed + // When we are a bit further, we only need to compensate the translation as it would be unnatural not to see the body rotating + const distanceOfNearestToControls = Vector3.Distance(nearestBody.getTransform().getAbsolutePosition(), controller.getTransform().getAbsolutePosition()); + const shouldCompensateTranslation = distanceOfNearestToControls < nearestBody.getBoundingRadius() * (nearestBody instanceof SpaceStation ? 80 : 10); + const shouldCompensateRotation = !(nearestBody instanceof SpaceStation) && distanceOfNearestToControls < nearestBody.getBoundingRadius() * 4; + + // ROTATION COMPENSATION + // If we have to compensate the rotation of the nearest body, there are multiple things to take into account + // The orbital plane of the body can be described using its normal vector. When the body is not rotating, the normal vector will rotate in its stead. + // You can draw a simple example to understand this: have a simple planet and its moon, but the moon's rotation axis on itself is tilted heavily. + // Therefore, we have to rotate all the orbital planes accordingly. + // Using the same example as before, it is trivial to see the planet will have to rotate around its moon. + // Adding more bodies, we see that all bodies must rotate around the fixed moon. + // By doing so, their rotation axis on themselves except the fixed one must as well be rotated in the same way. + // Last but not least, the background starfield must be rotated in the opposite direction to give the impression the moon is rotating. + if (shouldCompensateRotation) { + const dthetaNearest = OrbitalObject.GetRotationAngle(nearestBody, deltaTime); - // if we don't compensate the rotation of the nearest body, we must rotate it accordingly - if (!shouldCompensateRotation) OrbitalObject.UpdateRotation(nearestBody, deltaTime); + for (const object of this.orbitalObjects) { + const orbit = object.getOrbitProperties(); - // As the nearest object is kept in place, we need to transfer its movement to other bodies - for (const object of this.orbitalObjects) { - const orbit = object.getOrbitProperties(); - const oldOrbitNormal = orbit.normalToPlane.clone(); - if (shouldCompensateRotation) { // the normal to the orbit planes must be rotated as well (even the one of the nearest body) const rotation = Quaternion.RotationAxis(nearestBody.getRotationAxis(), -dthetaNearest); orbit.normalToPlane.applyRotationQuaternionInPlace(rotation); - } - if (object === nearestBody) continue; - if (shouldCompensateTranslation) { - // the body is translated so that the nearest body can stay in place - translate(object.getTransform(), nearestBodyDisplacement.negate()); - } + if (object === nearestBody) continue; - if (shouldCompensateRotation) { - // if the nearest body does not rotate, all other bodies must revolve around it for consistency + // All other bodies must revolve around it for consistency (finally we can say the sun revolves around the earth!) rotateAround(object.getTransform(), nearestBody.getTransform().getAbsolutePosition(), nearestBody.getRotationAxis(), -dthetaNearest); - - // we must as well rotate their rotation axis to keep consistency - const newNormal = orbit.normalToPlane.clone(); - const angle = Math.acos(Vector3.Dot(oldOrbitNormal, newNormal)); - if (angle > 0.02) { - // FIXME: when time goes very fast, this will get wrongfully executed - const axis = Vector3.Cross(oldOrbitNormal, newNormal); - const quaternion = Quaternion.RotationAxis(axis, angle); - const newRotationAxis = object.getRotationAxis().applyRotationQuaternion(quaternion); - setUpVector(object.getTransform(), newRotationAxis); - } } - } - if (shouldCompensateRotation) { - // the starfield is rotated to give the impression the nearest body is rotating, which is not the case + // the starfield is rotated to give the impression the nearest body is rotating, which is only an illusion const starfieldAdditionalRotation = Quaternion.RotationAxis(nearestBody.getRotationAxis(), dthetaNearest); this.universeRotation.copyFrom(starfieldAdditionalRotation.multiply(this.universeRotation)); + } else { + // if we don't compensate the rotation of the nearest body, we must simply update its rotation + OrbitalObject.UpdateRotation(nearestBody, deltaTime); + } + + // TRANSLATION COMPENSATION + // Compensating the translation is much easier in comparison. We save the initial position of the nearest body and + // compute what would be its next position if it were to move normally. + // This gives us a translation vector that we can negate and apply to all other bodies. + const initialPosition = nearestBody.getTransform().getAbsolutePosition().clone(); + const newPosition = OrbitalObject.GetNextOrbitalPosition(nearestBody, deltaTime); + const nearestBodyDisplacement = newPosition.subtract(initialPosition); + if (shouldCompensateTranslation) { + const negatedDisplacement = nearestBodyDisplacement.negate(); + for (const object of this.orbitalObjects) { + if (object === nearestBody) continue; + + // the body is translated so that the nearest body can stay in place + translate(object.getTransform(), negatedDisplacement); + } + } else { + // if we don't compensate the translation of the nearest body, we must simply update its position + translate(nearestBody.getTransform(), nearestBodyDisplacement); } // finally, all other objects are updated normally for (const object of this.orbitalObjects) { if (object === nearestBody) continue; - //object.updateInternalClock(deltaTime); OrbitalObject.UpdateOrbitalPosition(object, deltaTime); OrbitalObject.UpdateRotation(object, deltaTime); } diff --git a/src/ts/starSystem/starSystemHelper.ts b/src/ts/starSystem/starSystemHelper.ts index fc81f7f3b..d341b835c 100644 --- a/src/ts/starSystem/starSystemHelper.ts +++ b/src/ts/starSystem/starSystemHelper.ts @@ -34,6 +34,8 @@ import { getMoonSeed } from "../planets/common"; import { Planet } from "../architecture/planet"; import { StellarObject } from "../architecture/stellarObject"; import { BODY_TYPE } from "../model/common"; +import { SpaceStation } from "../spacestation/spaceStation"; +import { CelestialBody } from "../architecture/celestialBody"; export class StarSystemHelper { public static makeStar(starsystem: StarSystemController, model?: number | StarModel): Star { @@ -90,15 +92,20 @@ export class StarSystemHelper { */ public static makeStellarObject(starsystem: StarSystemController, seed: number = starsystem.model.getStarSeed(starsystem.stellarObjects.length)): StellarObject { const stellarObjectType = starsystem.model.getBodyTypeOfStar(starsystem.stellarObjects.length); - switch (stellarObjectType) { - case BODY_TYPE.BLACK_HOLE: - return StarSystemHelper.makeBlackHole(starsystem, seed); - case BODY_TYPE.NEUTRON_STAR: - return StarSystemHelper.makeNeutronStar(starsystem, seed); - case BODY_TYPE.STAR: - return StarSystemHelper.makeStar(starsystem, seed); - default: - throw new Error(`Unknown stellar object type ${stellarObjectType}`); + if(stellarObjectType === BODY_TYPE.BLACK_HOLE) { + const blackHole = StarSystemHelper.makeBlackHole(starsystem, seed); + StarSystemHelper.makeSpaceStations(starsystem, blackHole); + return blackHole; + } else if(stellarObjectType === BODY_TYPE.NEUTRON_STAR) { + const neutronStar = StarSystemHelper.makeNeutronStar(starsystem, seed); + StarSystemHelper.makeSpaceStations(starsystem, neutronStar); + return neutronStar; + } else if(stellarObjectType === BODY_TYPE.STAR) { + const star = StarSystemHelper.makeStar(starsystem, seed); + StarSystemHelper.makeSpaceStations(starsystem, star); + return star; + } else { + throw new Error(`Unknown stellar object type ${stellarObjectType}`); } } @@ -123,6 +130,7 @@ export class StarSystemHelper { ): TelluricPlanet { const planet = new TelluricPlanet(`${starsystem.model.getName()} ${romanNumeral(starsystem.planets.length + 1)}`, starsystem.scene, model, starsystem.stellarObjects[0]); starsystem.addTelluricPlanet(planet); + return planet; } @@ -141,22 +149,29 @@ export class StarSystemHelper { console.assert(n >= 0, `Cannot make a negative amount of planets : ${n}`); for (let i = 0; i < n; i++) { - switch (starsystem.model.getBodyTypeOfPlanet(starsystem.planets.length)) { - case BODY_TYPE.TELLURIC_PLANET: - StarSystemHelper.makeSatellites(starsystem, StarSystemHelper.makeTelluricPlanet(starsystem)); - break; - case BODY_TYPE.GAS_PLANET: - StarSystemHelper.makeSatellites(starsystem, StarSystemHelper.makeGasPlanet(starsystem)); - break; - case BODY_TYPE.MANDELBULB: - StarSystemHelper.makeMandelbulb(starsystem); - break; - default: - throw new Error(`Unknown body type ${starsystem.model.getBodyTypeOfPlanet(starsystem.planets.length)}`); + const bodyType = starsystem.model.getBodyTypeOfPlanet(starsystem.planets.length); + if(bodyType === BODY_TYPE.TELLURIC_PLANET) { + const planet = StarSystemHelper.makeTelluricPlanet(starsystem); + StarSystemHelper.makeSatellites(starsystem, planet); + StarSystemHelper.makeSpaceStations(starsystem, planet); + } else if(bodyType === BODY_TYPE.GAS_PLANET) { + const planet = StarSystemHelper.makeGasPlanet(starsystem); + StarSystemHelper.makeSatellites(starsystem, planet); + StarSystemHelper.makeSpaceStations(starsystem, planet); + } else { + throw new Error(`Unknown body type ${bodyType}`); } } } + public static makeSpaceStations(starsystem: StarSystemController, body: CelestialBody, n = body.model.getNbSpaceStations()): void { + console.assert(n >= 0, `Cannot make a negative amount of space stations : ${n}`); + for (let i = 0; i < n; i++) { + const spacestation = new SpaceStation(starsystem.scene, body); + starsystem.addSpaceStation(spacestation); + } + } + public static makeSatellite( starsystem: StarSystemController, planet: Planet, diff --git a/src/ts/starSystem/starSystemModel.ts b/src/ts/starSystem/starSystemModel.ts index d8a8ab48f..ec35e97da 100644 --- a/src/ts/starSystem/starSystemModel.ts +++ b/src/ts/starSystem/starSystemModel.ts @@ -83,7 +83,6 @@ export class StarSystemModel { } public getBodyTypeOfPlanet(index: number) { - if (uniformRandBool(0.01, this.rng, GENERATION_STEPS.CHOOSE_PLANET_TYPE + (index + 20) * 500)) return BODY_TYPE.MANDELBULB; if (uniformRandBool(0.5, this.rng, GENERATION_STEPS.CHOOSE_PLANET_TYPE + index)) return BODY_TYPE.TELLURIC_PLANET; return BODY_TYPE.GAS_PLANET; } diff --git a/src/ts/stellarObjects/blackHole/blackHoleModel.ts b/src/ts/stellarObjects/blackHole/blackHoleModel.ts index cf042bcc1..dbd54940b 100644 --- a/src/ts/stellarObjects/blackHole/blackHoleModel.ts +++ b/src/ts/stellarObjects/blackHole/blackHoleModel.ts @@ -18,7 +18,7 @@ import { seededSquirrelNoise } from "squirrel-noise"; import { getOrbitalPeriod } from "../../orbit/orbit"; import { Vector3 } from "@babylonjs/core/Maths/math.vector"; -import { normalRandom } from "extended-random"; +import { normalRandom, uniformRandBool } from "extended-random"; import { OrbitProperties } from "../../orbit/orbitProperties"; import { BODY_TYPE, GENERATION_STEPS } from "../../model/common"; import { BlackHolePhysicalProperties } from "../../architecture/physicalProperties"; @@ -71,4 +71,9 @@ export class BlackHoleModel implements StellarObjectModel { accretionDiskRadius: 8000e3 }; } + + public getNbSpaceStations(): number { + if(uniformRandBool(0.1, this.rng, GENERATION_STEPS.SPACE_STATION)) return 1; + return 0; + } } diff --git a/src/ts/stellarObjects/neutronStar/neutronStarModel.ts b/src/ts/stellarObjects/neutronStar/neutronStarModel.ts index f9b52f67c..e09286dcd 100644 --- a/src/ts/stellarObjects/neutronStar/neutronStarModel.ts +++ b/src/ts/stellarObjects/neutronStar/neutronStarModel.ts @@ -84,4 +84,9 @@ export class NeutronStarModel implements StellarObjectModel { this.ringsUniforms = null; } } + + public getNbSpaceStations(): number { + if(uniformRandBool(0.00001, this.rng, GENERATION_STEPS.SPACE_STATION)) return 1; + return 0; + } } diff --git a/src/ts/stellarObjects/star/starModel.ts b/src/ts/stellarObjects/star/starModel.ts index d86789d3a..1b4872d12 100644 --- a/src/ts/stellarObjects/star/starModel.ts +++ b/src/ts/stellarObjects/star/starModel.ts @@ -96,6 +96,11 @@ export class StarModel implements StellarObjectModel { this.color.copyFrom(getRgbFromTemperature(temperature)); } + public getNbSpaceStations(): number { + if(uniformRandBool(0.001, this.rng, GENERATION_STEPS.SPACE_STATION)) return 1; + return 0; + } + static getStellarTypeFromTemperature(temperature: number) { if (temperature < 3500) return STELLAR_TYPE.M; else if (temperature < 5000) return STELLAR_TYPE.K; diff --git a/src/ts/ui/helmetOverlay.ts b/src/ts/ui/helmetOverlay.ts index 3eb641863..992c68ecb 100644 --- a/src/ts/ui/helmetOverlay.ts +++ b/src/ts/ui/helmetOverlay.ts @@ -17,6 +17,7 @@ import overlayHTML from "../../html/helmetOverlay.html"; import { OrbitalObject } from "../architecture/orbitalObject"; +import { parseSpeed } from "../utils/parseToStrings"; export class HelmetOverlay { private parentNode: HTMLElement; @@ -41,4 +42,9 @@ export class HelmetOverlay { public update(currentBody: OrbitalObject) { this.bodyNamePlate.innerText = currentBody.name; } + + displaySpeed(shipInternalThrottle: number, shipTargetThrottle: number, speed: number) { + const throttleString = `${shipInternalThrottle.toFixed(0)}% | ${shipTargetThrottle.toFixed(0)}%`; + (document.querySelector("#speedometer") as HTMLElement).innerText = `${throttleString} | ${parseSpeed(speed)}`; + } } diff --git a/src/ts/utils/algebra.ts b/src/ts/utils/algebra.ts index 712170b02..f93a96dcf 100644 --- a/src/ts/utils/algebra.ts +++ b/src/ts/utils/algebra.ts @@ -46,11 +46,6 @@ export function getTransformationQuaternion(from: Vector3, to: Vector3): Quatern return Quaternion.RotationAxis(rotationAxis, angle); } -export function rotateVector3AroundInPlace(vector: Vector3, center: Vector3, axis: Vector3, angle: number): Vector3 { - const rotationQuaternion = Quaternion.RotationAxis(axis, angle); - return vector.subtractInPlace(center).applyRotationQuaternionInPlace(rotationQuaternion).addInPlace(center); -} - export function flattenVector3Array(vector3Array: Vector3[]): number[] { const result: number[] = []; for (const vector3 of vector3Array) { diff --git a/src/ts/utils/warpTunnel.ts b/src/ts/utils/warpTunnel.ts index 26da4fb92..8e6352bee 100644 --- a/src/ts/utils/warpTunnel.ts +++ b/src/ts/utils/warpTunnel.ts @@ -102,7 +102,7 @@ export class WarpTunnel implements Transformable { particle.position.addInPlace(direction.scale(Math.random() * 10)); particle.position.addInPlace(this.anchor.getAbsolutePosition()); - particle.velocity.copyFrom(direction.scale(300)); + particle.velocity.copyFrom(direction.scale(600)); particle.rotationQuaternion = rotationQuaternion; diff --git a/tests/unit/algebra.test.ts b/tests/unit/algebra.test.ts index c69012571..7e4db9b02 100644 --- a/tests/unit/algebra.test.ts +++ b/tests/unit/algebra.test.ts @@ -16,10 +16,8 @@ // along with this program. If not, see . import { Scene } from "@babylonjs/core/scene"; -import { Vector3 } from "@babylonjs/core/Maths/math"; import { NullEngine } from "@babylonjs/core/Engines/nullEngine"; import { TransformNode } from "@babylonjs/core/Meshes"; -import { rotateVector3AroundInPlace } from "../../src/ts/utils/algebra"; const engine = new NullEngine(); const scene = new Scene(engine); @@ -27,16 +25,4 @@ const scene = new Scene(engine); describe("BasicTransform", () => { const transform = new TransformNode("transform", scene); it("exists", () => expect(transform).toBeDefined()); - transform.setAbsolutePosition(new Vector3(10, 0, 0)); - - const pivotPoint = new Vector3(0, 5, 0); - const rotationAxis = new Vector3(0, 1, 0); - - const finalPosition1 = rotateVector3AroundInPlace(transform.getAbsolutePosition(), pivotPoint, rotationAxis, Math.PI / 2); - transform.rotateAround(pivotPoint, rotationAxis, Math.PI / 2); - const finalPosition2 = transform.getAbsolutePosition(); - - it("can rotate around a pivot", () => { - expect(finalPosition1.equalsWithEpsilon(finalPosition2)).toBeTruthy(); - }); }); \ No newline at end of file