import {ElementRef, Injectable} from '@angular/core'; import * as THREE from 'three'; import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls.js'; import {ThemeService} from '@shared-components/theme/theme.service'; import {THEME_MODE} from '@custom-types/constant'; /** * Service responsible for managing the Three.js rendering environment */ @Injectable({ providedIn: 'root' }) export class RendererService { scene!: THREE.Scene; camera!: THREE.PerspectiveCamera; renderer!: THREE.WebGLRenderer; controls!: OrbitControls; private currentTheme: THEME_MODE = THEME_MODE.LIGHT; constructor(private themeService: ThemeService) { this.themeService.theme$.subscribe(theme => { this.currentTheme = theme; if (this.scene) { this.setBackgroundColor(); } }) } /** * Initializes the Three.js scene, camera, renderer, and controls */ initializeRenderer(container: ElementRef): void { // Create scene this.scene = new THREE.Scene(); this.setBackgroundColor(); // Get container dimensions const containerWidth = container.nativeElement.clientWidth; const containerHeight = container.nativeElement.clientHeight; // Create camera this.camera = new THREE.PerspectiveCamera(75, containerWidth / containerHeight, 0.1, 1000); this.camera.position.set(-1, 2, 3); this.camera.lookAt(0, 1, 0); // Create renderer this.renderer = new THREE.WebGLRenderer({antialias: true}); this.renderer.setSize(containerWidth, containerHeight); // Center the canvas in the container this.renderer.domElement.style.display = 'block'; this.renderer.domElement.style.margin = 'auto'; container.nativeElement.appendChild(this.renderer.domElement); // Initialize orbit controls this.controls = new OrbitControls(this.camera, this.renderer.domElement); this.controls.enableDamping = true; this.controls.dampingFactor = 0.05; this.controls.minDistance = 0.5; this.controls.maxDistance = 4; this.controls.target.set(0, 1, 0); this.controls.update(); // Add lights this.addLights(); } private setBackgroundColor() { this.scene.background = new THREE.Color(this.currentTheme === THEME_MODE.DARK ? 0x242526 : 0xFBFBFE); } /** * Resets the camera to its default position and orientation. */ public resetCamera(): void { this.camera.position.set(-1, 2, 3); this.camera.lookAt(0, 1, 0); this.controls.target.set(0, 1, 0); this.controls.update(); } /** * Adds lighting to the scene */ private addLights(): void { const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); this.scene.add(ambientLight); const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8); directionalLight.position.set(1, 1, 1); this.scene.add(directionalLight); } /** * Handles window resize events */ onWindowResize(container: ElementRef): void { const containerWidth = container.nativeElement.clientWidth; const containerHeight = 400; // Fixed height as defined in CSS this.camera.aspect = containerWidth / containerHeight; this.camera.updateProjectionMatrix(); this.renderer.setSize(containerWidth, containerHeight); } /** * Renders the scene */ render(): void { if (this.controls) { this.controls.update(); } this.renderer.render(this.scene, this.camera); } }