/** * Railtrack Pro - Track Piece System * Defines track geometries and placement logic * @author Railtrack Pro Development Team */ class TrackPiece { constructor(type, position, rotation) { this.type = type; // 'straight', 'curved', 'junction', 'signal' this.id = position ? Math.floor(Math.random() * 10000) : null; this.position = position || new THREE.Vector3(0, 0, 0); this.rotation = rotation || new THREE.Vector3(0, Math.PI / 2, 0); this.connections = []; this.geometry = null; this.material = null; this.mesh = null; } /** * Create track piece in scene */ create(scene) { this.geometry = this.getGeometry(); this.material = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.6, metalness: 0.4 }); this.mesh = new THREE.Mesh(this.geometry, this.material); this.mesh.position.copy(this.position); this.mesh.rotation.copy(this.rotation); this.mesh.castShadow = true; this.mesh.receiveShadow = true; this.mesh.userData = { type: 'track', id: this.id, piece: this }; scene.add(this.mesh); return this; } /** * Get geometry based on track type */ getGeometry() { switch (this.type) { case 'straight': return new THREE.BoxGeometry(1, 0.3, 2); case 'curved': return this.createCurvedGeometry(); case 'junction': return this.createJunctionGeometry(); case 'signal': return this.createSignalGeometry(); default: return new THREE.BoxGeometry(1, 0.3, 2); } } /** * Create curved track geometry */ createCurvedGeometry() { const points = []; const segments = 20; const radius = 2; for (let i = 0; i <= segments; i++) { const angle = (i / segments) * Math.PI; const x = Math.sin(angle) * radius; const z = -Math.cos(angle) * radius + radius; points.push(new THREE.Vector3(x, 0, z)); } const curve = new THREE.CatmullRomCurve3(points); const geometry = new THREE.TubeGeometry(curve, segments, 0.5, 8, false); return geometry; } /** * Create junction track geometry (T-junction) */ createJunctionGeometry() { const group = new THREE.Group(); // Main stem const stem = new THREE.Mesh( new THREE.BoxGeometry(1, 0.3, 2), this.material ); stem.position.set(0, 0, 1); // Top branch const top = new THREE.Mesh( new THREE.BoxGeometry(1, 0.3, 2), this.material ); top.position.set(0, 0, -1); // Side branch const side = new THREE.Mesh( new THREE.BoxGeometry(2, 0.3, 1), this.material ); side.position.set(0, 0, 0); group.add(stem, top, side); const geometry = new THREE.Group(group); return geometry; } /** * Create signal track piece */ createSignalGeometry() { const group = new THREE.Group(); // Signal pole const pole = new THREE.Mesh( new THREE.CylinderGeometry(0.1, 0.1, 1.5), new THREE.MeshStandardMaterial({ color: 0x333333 }) ); pole.position.set(0, 0.75, 0); // Signal lights const lightBox = new THREE.Mesh( new THREE.BoxGeometry(0.3, 0.2, 0.1), new THREE.MeshStandardMaterial({ color: 0xff0000 }) ); lightBox.position.set(0, 1.5, 0); // Track base const base = new THREE.Mesh( new THREE.BoxGeometry(1, 0.3, 1), this.material ); base.position.set(0, 0, 0); group.add(base, pole, lightBox); const geometry = new THREE.Group(group); return geometry; } /** * Get connection points for track joining */ getConnectionPoints() { const points = []; switch (this.type) { case 'straight': points.push(new THREE.Vector3(0, 0, -1)); points.push(new THREE.Vector3(0, 0, 1)); break; case 'curved': points.push(new THREE.Vector3(0, 0, -2)); points.push(new THREE.Vector3(2, 0, 0)); break; case 'junction': points.push(new THREE.Vector3(0, 0, -2)); points.push(new THREE.Vector3(0, 0, 2)); points.push(new THREE.Vector3(-2, 0, 0)); points.push(new THREE.Vector3(2, 0, 0)); break; } return points; } } /** * Track type configuration */ const TrackConfig = { types: { straight: { name: 'Straight Track', color: 0x888888, cost: 10 }, curved: { name: 'Curved Track', color: 0x888888, cost: 15 }, junction: { name: 'Junction', color: 0x888888, cost: 25 }, signal: { name: 'Signal', color: 0xff0000, cost: 30 } }, placement: { snapDistance: 0.1, maxConnections: 4 } }; // Export for module usage if (typeof module !== 'undefined' && module.exports) { module.exports = { TrackPiece, TrackConfig }; }