import { Color3, Mesh, MeshBuilder, Scene, StandardMaterial, TransformNode, Vector3 } from "@babylonjs/core"; /** * Configuration options for the sight reticle */ export interface SightConfig { /** Position relative to parent */ position?: Vector3; /** Circle radius */ circleRadius?: number; /** Crosshair line length */ crosshairLength?: number; /** Line thickness */ lineThickness?: number; /** Reticle color */ color?: Color3; /** Rendering group ID */ renderingGroupId?: number; /** Gap size in the center of the crosshair */ centerGap?: number; } /** * Gun sight reticle with crosshair and circle */ export class Sight { private reticleGroup: TransformNode; private circle: Mesh; private crosshairLines: Mesh[] = []; private scene: Scene; private config: Required; // Default configuration private static readonly DEFAULT_CONFIG: Required = { position: new Vector3(0, 2, 125), circleRadius: 2, crosshairLength: 1.5, lineThickness: 0.1, color: Color3.Green(), renderingGroupId: 3, centerGap: 0.5 }; constructor(scene: Scene, parent: TransformNode, config?: SightConfig) { this.scene = scene; this.config = { ...Sight.DEFAULT_CONFIG, ...config }; this.createReticle(parent); } /** * Create the reticle (circle + crosshair) */ private createReticle(parent: TransformNode): void { // Create a parent node for the entire reticle this.reticleGroup = new TransformNode("sightReticle", this.scene); this.reticleGroup.parent = parent; this.reticleGroup.position = this.config.position; // Create material const material = new StandardMaterial("sightMaterial", this.scene); material.emissiveColor = this.config.color; material.disableLighting = true; material.alpha = 0.8; // Create outer circle this.circle = MeshBuilder.CreateTorus("sightCircle", { diameter: this.config.circleRadius * 2, thickness: this.config.lineThickness, tessellation: 64 }, this.scene); this.circle.parent = this.reticleGroup; this.circle.rotation.x = -Math.PI / 2; this.circle.material = material; this.circle.renderingGroupId = this.config.renderingGroupId; // Create crosshair lines (4 lines extending from center gap) this.createCrosshairLines(material); } /** * Create the crosshair lines (top, bottom, left, right) */ private createCrosshairLines(material: StandardMaterial): void { const gap = this.config.centerGap; const length = this.config.crosshairLength; const thickness = this.config.lineThickness; // Top line const topLine = MeshBuilder.CreateBox("crosshairTop", { width: thickness, height: length, depth: thickness }, this.scene); topLine.parent = this.reticleGroup; topLine.position.y = gap + length / 2; topLine.material = material; // topLine.renderingGroupId = this.config.renderingGroupId; this.crosshairLines.push(topLine); // Bottom line const bottomLine = MeshBuilder.CreateBox("crosshairBottom", { width: thickness, height: length, depth: thickness }, this.scene); bottomLine.parent = this.reticleGroup; bottomLine.position.y = -(gap + length / 2); bottomLine.material = material; // bottomLine.renderingGroupId = this.config.renderingGroupId; this.crosshairLines.push(bottomLine); // Left line const leftLine = MeshBuilder.CreateBox("crosshairLeft", { width: length, height: thickness, depth: thickness }, this.scene); leftLine.parent = this.reticleGroup; leftLine.position.x = -(gap + length / 2); leftLine.material = material; // leftLine.renderingGroupId = this.config.renderingGroupId; this.crosshairLines.push(leftLine); // Right line const rightLine = MeshBuilder.CreateBox("crosshairRight", { width: length, height: thickness, depth: thickness }, this.scene); rightLine.parent = this.reticleGroup; rightLine.position.x = gap + length / 2; rightLine.material = material; // rightLine.renderingGroupId = this.config.renderingGroupId; this.crosshairLines.push(rightLine); // Center dot (optional, very small) const centerDot = MeshBuilder.CreateSphere("crosshairCenter", { diameter: thickness * 1.5 }, this.scene); centerDot.parent = this.reticleGroup; centerDot.material = material; // centerDot.renderingGroupId = this.config.renderingGroupId; this.crosshairLines.push(centerDot); } /** * Set visibility of the sight */ public setVisible(visible: boolean): void { this.circle.isVisible = visible; this.crosshairLines.forEach(line => line.isVisible = visible); } /** * Change the sight color */ public setColor(color: Color3): void { this.config.color = color; const material = this.circle.material as StandardMaterial; if (material) { material.emissiveColor = color; } } /** * Get the reticle group transform node */ public getTransformNode(): TransformNode { return this.reticleGroup; } /** * Dispose of the sight */ public dispose(): void { this.circle.dispose(); this.crosshairLines.forEach(line => line.dispose()); this.reticleGroup.dispose(); } }