Skip to content

Solar System

A simple scene of our solar system (distances not to scale).

Code
ts
import {
    CanvasApp,
    Color,
    Group,
    LineBatcher,
    LineObject,
    MeshBatcher,
    MeshObject,
    PI_2,
    SceneGraphObject,
    TexturedMaterial,
    Vertex,
    WGLDriver,
    WGLLineRenderer,
    WGLTexturedMeshBatchRenderer,
    buildCirclePoints,
    buildTriangulatedMesh,
} from '../../src';

interface SpaceObject {
    name: string;
    color: Color;
    diameter: number;
    distance: number;
    velocity: number;
}

const SPACE_OBJECTS: SpaceObject[] = [
    {
        name: 'Sun',
        color: Color.fromHexString('#FFFDEE'),
        diameter: 696_300 * 2,
        distance: 0,
        velocity: 0,
    },
    {
        name: 'Jupiter',
        color: Color.fromHexString('#D3D2B9'),
        diameter: 142_984,
        distance: 778_000_000,
        velocity: 1.673e-1,
    },
    {
        name: 'Saturn',
        color: Color.fromHexString('#CCA270'),
        diameter: 120_536,
        distance: 1_400_000_000,
        velocity: 9.294e-2,
    },
    {
        name: 'Uranus',
        color: Color.fromHexString('#D4EEF0'),
        diameter: 51_118,
        distance: 2_900_000_000,
        velocity: 2.37e-2,
    },
    {
        name: 'Neptune',
        color: Color.fromHexString('#657CF3'),
        diameter: 49_528,
        distance: 4_500_000_000,
        velocity: 1.208e-2,
    },
    {
        name: 'Earth',
        color: Color.fromHexString('#78B436'),
        diameter: 12_756,
        distance: 149_000_000,
        velocity: 1.992,
    },
    {
        name: 'Venus',
        color: Color.fromHexString('#CDBEB0'),
        diameter: 12_104,
        distance: 108_000_000,
        velocity: 3.232,
    },
    {
        name: 'Mars',
        color: Color.fromHexString('#EFB166'),
        diameter: 6_792,
        distance: 79_000_000,
        velocity: 1.059,
    },
    {
        name: 'Mercury',
        color: Color.fromHexString('#959192'),
        diameter: 4_880,
        distance: 58_000_000,
        velocity: 8.264,
    },
];

const MAX_DIAMETER = SPACE_OBJECTS.reduce(
    (max, spaceObject) =>
        spaceObject.diameter > max ? spaceObject.diameter : max,
    0,
);

const MAX_DIAMETER_PIXELS = 312;

function pixelDiameter(d: number) {
    return (d / MAX_DIAMETER) * MAX_DIAMETER_PIXELS;
}

interface RenderSpaceObject {
    root: SceneGraphObject;
    meshObject: MeshObject;
    lineObject: LineObject;
    spaceObject: SpaceObject;
    angle: number;
    radius: number;
}

const BACKGROUND_COLOR = Color.fromHexString('#111');

export class SceneApp extends CanvasApp<WGLDriver> {
    private readonly meshRenderer = new WGLTexturedMeshBatchRenderer(
        this.driver,
    );
    private readonly lineRenderer = new WGLLineRenderer(this.driver);

    // #region mesh-batcher
    private readonly meshBatches = new MeshBatcher({
        maxTextureCount: this.driver.textures.maxTextureCount,
        changeTracker: this.changeTracker,
    });
    // #endregion mesh-batcher
    private readonly lineBatches = new LineBatcher({
        changeTracker: this.changeTracker,
    });

    private readonly objects: RenderSpaceObject[] = [];

    override async initialize(): Promise<void> {
        await super.initialize();

        let distance = 0;
        let last: RenderSpaceObject | undefined;

        for (const so of SPACE_OBJECTS.toSorted(
            (a, b) => a.distance - b.distance,
        )) {
            const radius = pixelDiameter(so.diameter) / 2;

            if (last) {
                distance += last.radius + 12 + radius;
            }

            const meshObject = new MeshObject({
                x: distance,
                mesh: buildTriangulatedMesh(
                    buildCirclePoints(0, 0, radius, 4).map(p => new Vertex(p)),
                ),
                material: new TexturedMaterial(this.textures.white, so.color),
            });

            const lineObject = new LineObject({
                points: buildCirclePoints(0, 0, distance, 4, 'distance', true),
                style: {
                    color: new Color(1, 1, 1, 0.1),
                },
            });

            const initialRotation = Math.random() * PI_2;

            const root = new Group({ radians: initialRotation });

            root.add(meshObject, lineObject);

            this.transforms.change(root);

            // #region add-to-mesh-batcher
            this.meshBatches.add(meshObject);
            // #endregion add-to-mesh-batcher
            this.lineBatches.add(lineObject);

            this.objects.push({
                meshObject,
                lineObject,
                root,
                spaceObject: so,
                angle: 0,
                radius,
            });

            last = this.objects.at(-1);
        }

        this.tickers.add(({ elapsedSeconds }) => {
            for (const so of this.objects) {
                so.root.transform.radians +=
                    so.spaceObject.velocity * elapsedSeconds;

                if (so.root.transform.radians > PI_2) {
                    so.root.transform.radians = 0;
                }

                this.transforms.change(so.root);
                this.meshBatches.change(so.meshObject);
            }
        });
    }

    protected override render(): void {
        super.render();

        this.driver.useRenderTarget('canvas');

        this.driver.clear(BACKGROUND_COLOR);

        this.lineRenderer.render(this.lineBatches.finalize(), this.camera);

        // #region finalize-mesh-batches
        const meshBatches = this.meshBatches.finalize();
        // #endregion finalize-mesh-batches

        // #region render-mesh-batches
        this.meshRenderer.render(meshBatches, this.camera);
        // #endregion render-mesh-batches
    }
}