Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve spaceship automatic landing #305

Merged
merged 30 commits into from
Feb 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
89be09e
add unique id to spaceship
BarthPaleologue Feb 9, 2025
d430bfa
make notification system more robust
BarthPaleologue Feb 9, 2025
a9e9c8a
avoid spaceship speed oscillating around 0
BarthPaleologue Feb 9, 2025
ee69c3a
Landing computer prototype
BarthPaleologue Feb 9, 2025
c23272e
Localize some spaceship notifications
BarthPaleologue Feb 10, 2025
ad1ab1d
Avoid space station spawning inside rings
BarthPaleologue Feb 10, 2025
54d2dde
ignore user inputs when in autopilot
BarthPaleologue Feb 10, 2025
9df4220
Fix landing cancellation when in autopilot
BarthPaleologue Feb 10, 2025
8c7cc7b
extract some heavy logic from spaceship
BarthPaleologue Feb 10, 2025
d73ce61
set main engine throttle to 0 when in autopilot
BarthPaleologue Feb 10, 2025
9672944
add timeout error for landing computer
BarthPaleologue Feb 10, 2025
136690a
Fix emergency warp drive stop
BarthPaleologue Feb 10, 2025
9360f2c
Fix planet landing ship not knowing its updated position
BarthPaleologue Feb 10, 2025
f82a08a
always compensate closest object translation
BarthPaleologue Feb 10, 2025
cd59e6c
harder velocity damping
BarthPaleologue Feb 12, 2025
3baabd5
allow landing computer to land on any transformnode instead of every …
BarthPaleologue Feb 12, 2025
e2355b6
add alternate ground to automatic landing playground
BarthPaleologue Feb 12, 2025
835066a
tweak landing computer values
BarthPaleologue Feb 12, 2025
e90ec59
Ship controls apply impulse instead of changing velocity
BarthPaleologue Feb 12, 2025
69f6d14
Test landing computer
BarthPaleologue Feb 12, 2025
517c4d5
Offset the landing spot position by half the bounding box extent y of…
BarthPaleologue Feb 13, 2025
841bf6e
Make landing computer robust to errors
BarthPaleologue Feb 13, 2025
f433214
increase planet landing tolerance
BarthPaleologue Feb 13, 2025
2601ebe
Improve lens flare fade out
BarthPaleologue Feb 13, 2025
ca2c514
split landing speed accross axes
BarthPaleologue Feb 13, 2025
d10174b
Fix landing spot rotation for surface landings
BarthPaleologue Feb 13, 2025
a5415d4
Reduce surface landing speed
BarthPaleologue Feb 13, 2025
c7fdaeb
Fix missing collision masks on some parts of the ship
BarthPaleologue Feb 15, 2025
a409a78
Better velocity damping
BarthPaleologue Feb 15, 2025
b2c12b7
Prevent player from going too fast before landing
BarthPaleologue Feb 15, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/html/helmetOverlay.html
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,3 @@
<img src="../asset/icons/fuel_canister.webp" alt="Fuel canister icon" />
</div>
</section>

<div id="notificationContainer"></div>
2 changes: 2 additions & 0 deletions src/locales/en-US/notifications.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"landingSequenceEngaged": "Landing sequence engaged",
"landingComplete": "Landing complete! Use {{bindingsString}} to disembark.",
"landingCancelled": "Landing cancelled",
"autoPilotEngaged": "Auto-pilot engaged. Sit back and relax.",
"takeOffSuccess": "Take off successful",
"copiedToClipboard": "Copied to clipboard",
"howToLiftOff": "Hold {{bindingsString}} to lift off.",
"howToHyperSpace": "Align your ship with the system target using the target helper on the bottom right of your screen then press {{bindingsString}} to make a hyperspace jump.",
Expand Down
2 changes: 2 additions & 0 deletions src/locales/fr-FR/notifications.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
"landingSequenceEngaged": "Début de la séquence d'atterrissage.",
"landingComplete": "Atterrissage réussi ! Utilisez {{bindingsString}} pour sortir du vaisseau.",
"landingCancelled": "Atterrissage annulé.",
"autoPilotEngaged": "Pilote automatique activé. Détendez-vous.",
"takeOffSuccess": "Décollage réussi.",
"copiedToClipboard": "Copié dans le presse-papiers.",
"howToLiftOff": "Maintenez {{bindingsString}} pour décoller.",
"howToHyperSpace": "Alignez votre vaisseau avec la cible du système de destination à l'aide du radar de cible en bas à droite de l'écran. Appuyez ensuite sur {{bindingsString}} pour enclencher le saut hyperspatial.",
Expand Down
26 changes: 13 additions & 13 deletions src/shaders/lensflare.glsl
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ float getSun(vec2 uv){
#define CHEAP_FLARE

//from: https://www.shadertoy.com/view/XdfXRX
vec3 lensflares(vec2 uv, vec2 pos)
vec3 lensflares(vec2 uv, vec2 pos, float fadeOut)
{
vec2 main = uv-pos;
vec2 uvd = uv*(length(uv));
Expand All @@ -42,6 +42,8 @@ vec3 lensflares(vec2 uv, vec2 pos)

f0 = f0+f0*(sin((ang+1.0/18.0)*12.0)*.1+dist*.1+.8);

f0 *= fadeOut;

float f2 = max(1.0/(1.0+32.0*pow(length(uvd+0.8*pos), 2.0)), .0)*00.25;
float f22 = max(1.0/(1.0+32.0*pow(length(uvd+0.85*pos), 2.0)), .0)*00.23;
float f23 = max(1.0/(1.0+32.0*pow(length(uvd+0.9*pos), 2.0)), .0)*00.21;
Expand Down Expand Up @@ -73,15 +75,15 @@ vec3 lensflares(vec2 uv, vec2 pos)


// based on https://www.shadertoy.com/view/XsGfWV
vec3 anflares(vec2 uv, float threshold, float intensity, float stretch, float brightness)
vec3 anflares(vec2 uv, float threshold, float intensity, float stretch, float brightness, float fadeOut)
{
threshold = 1.0 - threshold;

vec3 hdr = vec3(getSun(uv));
hdr = vec3(floor(threshold+pow(hdr.r, 1.0)));

float d = intensity;
float c = intensity*stretch;
float d = intensity * fadeOut;
float c = intensity * stretch;

for (float i=c; i>-1.0; i--){
float texL = getSun(uv+vec2(i/d, 0.0));
Expand Down Expand Up @@ -129,28 +131,26 @@ void main() {

vec3 col = screenColor.rgb;

vec3 flare = lensflares(uv*1.5, mouse*1.5);
// if angular radius is to great, fade the anflare out
float angularRadius = object_radius / length(object_position - camera_position);
float fadeOut = 1.0 - smoothstep(0.0, 0.1, angularRadius);

vec3 flare = lensflares(uv*1.5, mouse*1.5, fadeOut);

#ifdef CHEAP_FLARE
vec3 anflare = pow(anflares(uv-mouse, 400.0, 0.5, 0.6), vec3(4.0));
vec3 anflare = pow(anflares(uv-mouse, 400.0, 0.5, 0.6) * fadeOut, vec3(4.0));
anflare += smoothstep(0.0025, 1.0, anflare)*10.0;
anflare *= smoothstep(0.0, 1.0, anflare);
#else
vec3 anflare = pow(anflares(uv-mouse, 0.5, 400.0, 0.9, 0.1), vec3(4.0));
#endif

// if angular radius is to great, fade the anflare out
float angularRadius = object_radius / length(object_position - camera_position);
anflare *= 1.0 - smoothstep(0.0, 0.1, angularRadius);

vec3 sun = getSun(uv-mouse) + (flare + anflare)*flareColor*2.0;
vec3 sun = getSun(uv-mouse) * fadeOut + (flare + anflare)*flareColor*2.0;

// no lensflare when looking away from the sun
sun *= smoothstep(0.0, 0.1, dot(objectDirection, rayDir));

// no lensflare when too close to the sun
sun *= 1.0 - smoothstep(0.0, 0.08, angularRadius);

col += sun * visibility;

// Output to screen
Expand Down
5 changes: 3 additions & 2 deletions src/ts/planets/telluricPlanet/telluricPlanet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@ export class TelluricPlanet
{
readonly model: TelluricPlanetModel | TelluricSatelliteModel;

readonly type: OrbitalObjectType.TELLURIC_PLANET | OrbitalObjectType.TELLURIC_SATELLITE =
OrbitalObjectType.TELLURIC_PLANET | OrbitalObjectType.TELLURIC_SATELLITE;
readonly type: OrbitalObjectType.TELLURIC_PLANET | OrbitalObjectType.TELLURIC_SATELLITE;

readonly sides: ChunkTree[]; // stores the 6 sides of the sphere

Expand Down Expand Up @@ -77,6 +76,8 @@ export class TelluricPlanet
constructor(model: TelluricPlanetModel | TelluricSatelliteModel, scene: Scene) {
this.model = model;

this.type = model.type;

this.transform = new TransformNode(this.model.name, scene);
this.transform.rotationQuaternion = Quaternion.Identity();

Expand Down
29 changes: 27 additions & 2 deletions src/ts/playgrounds/automaticLanding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,22 @@ import { HemisphericLight } from "@babylonjs/core/Lights/hemisphericLight";
import { Vector3 } from "@babylonjs/core/Maths/math.vector";
import { LandingPad, LandingPadSize } from "../assets/procedural/landingPad/landingPad";
import { Transformable } from "../architecture/transformable";
import { AssetsManager, TransformNode } from "@babylonjs/core";
import {
AssetsManager,
MeshBuilder,
PhysicsAggregate,
PhysicsShapeType,
Quaternion,
TransformNode
} from "@babylonjs/core";
import { enablePhysics } from "./utils";
import { DefaultControls } from "../defaultControls/defaultControls";
import { Spaceship } from "../spaceship/spaceship";
import { Objects } from "../assets/objects";
import { Textures } from "../assets/textures";
import { Sounds } from "../assets/sounds";
import { randRange } from "extended-random";
import { CollisionMask } from "../settings";

export async function createAutomaticLandingScene(engine: AbstractEngine): Promise<Scene> {
const scene = new Scene(engine);
Expand All @@ -42,7 +51,12 @@ export async function createAutomaticLandingScene(engine: AbstractEngine): Promi
await assetsManager.loadAsync();

const ship = Spaceship.CreateDefault(scene);
ship.getTransform().position.copyFromFloats(0, 20, 0);
ship.getTransform().position.copyFromFloats(
randRange(-50, 50, Math.random, 0),
randRange(30, 50, Math.random, 0),
randRange(-50, 50, Math.random, 0)
);
ship.getTransform().rotationQuaternion = Quaternion.Random().normalize();

const defaultControls = new DefaultControls(scene);
defaultControls.getTransform().position.copyFromFloats(0, 10, -50);
Expand All @@ -54,6 +68,14 @@ export async function createAutomaticLandingScene(engine: AbstractEngine): Promi

const landingPad = new LandingPad(42, LandingPadSize.SMALL, scene);

const ground = MeshBuilder.CreateBox("ground", { width: 100, height: 1, depth: 100 }, scene);
ground.position.y = -2;
ground.position.x = 75;

const groundAggregate = new PhysicsAggregate(ground, PhysicsShapeType.BOX, { mass: 0 }, scene);
groundAggregate.shape.filterMembershipMask = CollisionMask.ENVIRONMENT;
groundAggregate.shape.filterCollideMask = CollisionMask.DYNAMIC_OBJECTS;

const hemi = new HemisphericLight("hemi", Vector3.Up(), scene);
hemi.intensity = 1.0;

Expand All @@ -65,6 +87,9 @@ export async function createAutomaticLandingScene(engine: AbstractEngine): Promi
dispose: () => sunTransform.dispose()
};

//ship.engageLandingOnPad(landingPad);
ship.engageSurfaceLanding(ground);

scene.onBeforeRenderObservable.add(() => {
const deltaSeconds = engine.getDeltaTime() / 1000;

Expand Down
146 changes: 146 additions & 0 deletions src/ts/spaceship/landingComputer.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { LandingComputer, LandingTargetKind, LandingComputerStatusBit } from "./landingComputer";
import { Vector3, Quaternion } from "@babylonjs/core/Maths/math.vector";
import { PhysicsAggregate } from "@babylonjs/core/Physics/v2/physicsAggregate";
import { PhysicsEngineV2 } from "@babylonjs/core/Physics/v2";
import { TransformNode } from "@babylonjs/core/Meshes/transformNode";
import { LandingPad } from "../assets/procedural/landingPad/landingPad";
import { PhysicsBody } from "@babylonjs/core/Physics/v2/physicsBody";

// Mock BabylonJS classes
vi.mock("@babylonjs/core/Physics/v2/physicsAggregate", () => ({
PhysicsAggregate: vi.fn()
}));

vi.mock("@babylonjs/core/Physics/v2/physicsBody", () => ({
PhysicsBody: vi.fn()
}));

describe("LandingComputer", () => {
let landingComputer: LandingComputer;
let mockAggregate: PhysicsAggregate;
let mockPhysicsEngine: PhysicsEngineV2;
let mockTransform: TransformNode;
let mockPhysicsBody: PhysicsBody;

beforeEach(() => {
// Setup mocks
mockTransform = {
getAbsolutePosition: vi.fn().mockReturnValue(new Vector3(0, 0, 0)),
getHierarchyBoundingVectors: vi.fn().mockReturnValue({
min: new Vector3(-1, -1, -1),
max: new Vector3(1, 1, 1)
}),
absoluteRotationQuaternion: new Quaternion(),
up: Vector3.Up(),
right: Vector3.Right(),
forward: Vector3.Forward(),
position: new Vector3(0, 0, 0)
} as unknown as TransformNode;

mockPhysicsBody = {
getLinearVelocity: vi.fn().mockReturnValue(new Vector3(0, 0, 0)),
getAngularVelocity: vi.fn().mockReturnValue(new Vector3(0, 0, 0)),
getMassProperties: vi.fn().mockReturnValue({ mass: 1 }),
applyForce: vi.fn(),
applyAngularImpulse: vi.fn()
} as unknown as PhysicsBody;

mockAggregate = {
transformNode: mockTransform,
body: mockPhysicsBody
} as unknown as PhysicsAggregate;

mockPhysicsEngine = {
raycastToRef: vi.fn().mockImplementation((start, end, result) => {
result.hasHit = true;
result.hitPointWorld = new Vector3(0, 0, 0);
result.hitNormalWorld = Vector3.Up();
})
} as unknown as PhysicsEngineV2;

landingComputer = new LandingComputer(mockAggregate, mockPhysicsEngine);
});

it("should initialize with null target", () => {
expect(landingComputer.getTarget()).toBeNull();
});

it("should set landing pad target", () => {
const mockLandingPad = {
getTransform: () => ({
getAbsolutePosition: () => new Vector3(0, 10, 0),
up: Vector3.Up(),
absoluteRotationQuaternion: new Quaternion()
}),
padHeight: 2
} as unknown as LandingPad;

landingComputer.setTarget({
kind: LandingTargetKind.LANDING_PAD,
landingPad: mockLandingPad
});

expect(landingComputer.getTarget()).not.toBeNull();
expect(landingComputer.getTarget()?.kind).toBe(LandingTargetKind.LANDING_PAD);
});

it("should set celestial body target", () => {
const mockCelestialBody = {
position: new Vector3(0, 100, 0)
} as unknown as TransformNode;

landingComputer.setTarget({
kind: LandingTargetKind.CELESTIAL_BODY,
celestialBody: mockCelestialBody
});

expect(landingComputer.getTarget()).not.toBeNull();
expect(landingComputer.getTarget()?.kind).toBe(LandingTargetKind.CELESTIAL_BODY);
});

it("should return IDLE status when no target", () => {
const status = landingComputer.update(0.016);
expect(status).toBe(LandingComputerStatusBit.IDLE);
});

it("should timeout after max seconds", () => {
const mockLandingPad = {
getTransform: () => ({
getAbsolutePosition: () => new Vector3(0, 10, 0),
up: Vector3.Up(),
absoluteRotationQuaternion: new Quaternion()
}),
padHeight: 2
} as unknown as LandingPad;

landingComputer.setTarget({
kind: LandingTargetKind.LANDING_PAD,
landingPad: mockLandingPad
});

// Simulate passage of time beyond timeout
const status = landingComputer.update(91);
expect(status).toBe(LandingComputerStatusBit.TIMEOUT);
expect(landingComputer.getTarget()).toBeNull();
});

it("should report progress during normal operation", () => {
const mockLandingPad = {
getTransform: () => ({
getAbsolutePosition: () => new Vector3(0, 10, 0),
up: Vector3.Up(),
absoluteRotationQuaternion: new Quaternion()
}),
padHeight: 2
} as unknown as LandingPad;

landingComputer.setTarget({
kind: LandingTargetKind.LANDING_PAD,
landingPad: mockLandingPad
});

const status = landingComputer.update(0.016);
expect(status).toBe(LandingComputerStatusBit.PROGRESS);
});
});
Loading
Loading