Source: view/Simulator.js

import * as THREE from "three";
import Resources from "../model/Utils/Resources/Resources";
import Sizes from "../model/Utils/Sizes";
import Time from "../model/Utils/Time";
import Renderer from "./Renderer";
import World from "./World";
import Sources from "../model/Utils/Resources/Sources";
import { Events } from "../controller/Utils/Events";
import Camera from "./Camera";
import Environment from "../model/Environment";
import Physics from "../controller/Physics";
import Submarines from "../model/Submarines";
import GUI from 'lil-gui';
import SubmarineView from '../view/SubmarineView';
/**
 * Simulator class for managing the WebGL-based simulation environment.
 *
 * This class integrates components for scene rendering, resource management,
 * time handling, debugging, and world simulation.
 *
 * @class
 */
class Simulator {
  /**
   * Creates an instance of the Simulator class.
   * Initializes all necessary components and sets up event listeners.
   *
   * @param {HTMLCanvasElement} canvas - The HTML canvas element for rendering.
   * @constructor
   */
  constructor(canvas) {
    /**
     * Singleton instance of the Simulator class.
     *
     * @type {Simulator}
     * @private
     */
    this.instance = null;
    // Singleton pattern
    if (this.instance) {
      return this.instance;
    }
    this.instance = this;
    // Set canvas
    this.canvas = canvas;
    // Initialize components
    this.sizes = new Sizes();
    this.time = new Time();
    this.scene = new THREE.Scene();
    this.resources = new Resources(Sources);
    this.camera = new Camera(this);
    this.renderer = new Renderer(this);
    this.world = new World(this);
    this.environment = new Environment(9.8, 1025);
    this.submarines = new Submarines();
    this.physics = new Physics(this);
    this.time.on(Events.ClockTick, () => this.physics.simulateStep());
    // Subscribe to resize event
    this.sizes.on(Events.Resize, () => this.resize());
    // Subscribe to frame tick event
    this.time.on(Events.FrameTick, () => this.update());

    // Setup the loading screen with lil-gui
    this.setupLoadingScreen();

    // Listen for resource loading events
    this.resources.on(Events.ResourceProgress, () => {
      this.gui.controllers[0].setValue(this.resources.progress * 100);
    });

    this.resources.on(Events.ResourcesReady, () => {
      // Remove or hide the loading screen once resources are ready
      this.gui.destroy();
    });
  }
  setupLoadingScreen() {
    // Initialize lil-gui
    this.gui = new GUI({ title: 'Loading...', autoPlace: false });

    document.body.appendChild(this.gui.domElement);

    // Apply custom CSS class for better styling
    this.gui.domElement.classList.add('loading');

    // Use a number display instead of a slider
    this.progressController = this.gui.add({ Progress: 0 }, 'Progress', 0, 100).step(1).listen().disable();

  }
  /**
   * Handles resize event by updating camera and renderer dimensions.
   *
   * @public
   */
  resize() {
    this.camera.resize();
    this.renderer.resize();
  }
  /**
   * Updates the simulation state on each frame tick.
   *
   * @public
   */
  update() {
    this.camera.update();
    this.world.update();
    this.renderer.update();
  }
  /**
   * Destroys the simulator instance and cleans up resources.
   *
   * This method unsubscribes from events, disposes resources, and clears scene objects.
   *
   * @public
   */
  destroy() {
    // Unsubscribe from events
    this.sizes.off(Events.Resize);
    this.time.off(Events.FrameTick);
    this.time.off(Events.ClockTick);
    this.time.off(Events.ResourcesReady);
    // Traverse scene and dispose objects
    this.scene.traverse((child) => {
      if (child instanceof THREE.Mesh) {
        child.geometry.dispose();
        for (const key in child.material) {
          const value = child.material[key];
          if (value && typeof value.dispose === "function") {
            value.dispose();
          }
        }
      }
    });
    // Dispose camera, renderer, and debug
    this.camera.dispose();
    this.renderer.dispose();
    if (this.controlGUI.active) {
      this.controlGUI.dispose();
    }
  }
}
export default Simulator;