Files
railtrack-pro/js/train.js
T

226 lines
6.6 KiB
JavaScript
Raw Normal View History

/**
* Train Module
* Handles train model and controller for driving along tracks
* Works in browser environment with Three.js global
* @module train
*/
/**
* Train class - represents a train that can move along tracks
*/
class Train {
/**
* Create a train instance
* @param {Object} options - Train configuration options
* @param {string} options.id - Unique identifier for the train
* @param {number} options.speed - Initial speed
* @param {number} options.maxSpeed - Maximum speed
* @param {number} options.direction - Direction (1 or -1)
* @param {number} options.length - Train length for display
* @param {string} options.color - Train body color
*/
constructor(options = {}) {
this.id = options.id || `train-${Date.now()}`;
this.speed = options.speed || 0;
this.maxSpeed = options.maxSpeed || 5;
this.acceleration = options.acceleration || 0.5;
this.brakeDeceleration = options.brakeDeceleration || 0.3;
this.direction = options.direction || 1;
this.length = options.length || 3;
this.isMoving = false;
this.threeMesh = null;
this.color = options.color || '#FF6B00';
}
/**
* Accelerate the train
*/
accelerate() {
this.speed = Math.min(this.speed + this.acceleration * this.direction, this.maxSpeed * this.direction);
this.isMoving = this.speed !== 0;
}
/**
* Brake the train
*/
brake() {
const brakeAmount = this.brakeDeceleration * this.direction;
const newSpeed = this.speed - brakeAmount;
if (Math.abs(newSpeed) < Math.abs(this.brakeDeceleration)) {
this.speed = 0;
this.isMoving = false;
} else {
this.speed = newSpeed;
}
}
/**
* Reverse direction
*/
reverse() {
this.direction *= -1;
}
/**
* Stop the train
*/
stop() {
this.speed = 0;
this.isMoving = false;
}
/**
* Update train physics based on speed
* @param {number} deltaTime - Time delta in seconds
*/
update(deltaTime) {
if (this.isMoving) {
this.speed = this.speed > 0 ? Math.max(this.speed - 0.1 * this.direction, 0) : Math.min(this.speed + 0.1 * this.direction, 0);
this.isMoving = this.speed !== 0;
}
}
/**
* Get train velocity
* @returns {number} Train velocity
*/
getVelocity() {
return this.speed;
}
}
/**
* TrainController class - manages train position along track path
*/
class TrainController {
/**
* Create a train controller
* @param {Array} trackPath - Array of {x, y, z} points defining the track path
*/
constructor(trackPath = []) {
this.trackPath = trackPath || [];
this.progress = 0; // 0.0 to 1.0 along the path
this.distance = 0; // Distance traveled along path
this.currentPosition = { x: 0, y: 0, z: 0 };
this.totalPathLength = this._calculatePathLength();
}
/**
* Calculate total path length
* @returns {number} Total length of the track path
* @private
*/
_calculatePathLength() {
if (this.trackPath.length < 2) return 0;
let totalLength = 0;
for (let i = 1; i < this.trackPath.length; i++) {
const p1 = this.trackPath[i - 1];
const p2 = this.trackPath[i];
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const dz = p2.z - p1.z;
totalLength += Math.sqrt(dx * dx + dy * dy + dz * dz);
}
return totalLength;
}
/**
* Set distance traveled along the path
* @param {number} distance - Distance to set
*/
setDistance(distance) {
this.distance = Math.max(0, Math.min(distance, this.totalPathLength));
this.progress = this.totalPathLength > 0 ? this.distance / this.totalPathLength : 0;
this.updateCurrentPosition();
}
/**
* Get current position on the track path
* @returns {Object} Position {x, y, z}
*/
getCurrentPosition() {
return this.currentPosition;
}
/**
* Update current position based on progress
* @private
*/
updateCurrentPosition() {
if (this.trackPath.length < 2) {
this.currentPosition = this.trackPath[0] || { x: 0, y: 0, z: 0 };
return;
}
const totalLength = this.totalPathLength;
const targetDistance = this.progress * totalLength;
let distance = 0;
for (let i = 0; i < this.trackPath.length - 1; i++) {
const p1 = this.trackPath[i];
const p2 = this.trackPath[i + 1];
const p1Length = distance;
const p2Length = distance + this._distanceBetween(p1, p2);
if (targetDistance >= p1Length && targetDistance <= p2Length) {
const segmentProgress = (targetDistance - p1Length) / (p2Length - p1Length);
this.currentPosition = {
x: p1.x + (p2.x - p1.x) * segmentProgress,
y: p1.y + (p2.y - p1.y) * segmentProgress,
z: p1.z + (p2.z - p1.z) * segmentProgress
};
return;
}
distance += this._distanceBetween(p1, p2);
}
// End of path
this.currentPosition = this.trackPath[this.trackPath.length - 1];
}
/**
* Calculate distance between two points
* @param {Object} p1 - First point {x, y, z}
* @param {Object} p2 - Second point {x, y, z}
* @returns {number} Distance
* @private
*/
_distanceBetween(p1, p2) {
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const dz = p2.z - p1.z;
return Math.sqrt(dx * dx + dy * dy + dz * dz);
}
/**
* Update controller based on train speed
* @param {number} deltaTime - Time delta in seconds
* @param {number} speed - Train speed
*/
update(deltaTime, speed) {
const distanceDelta = speed * deltaTime;
this.setDistance(this.distance + distanceDelta);
}
/**
* Set track path
* @param {Array} path - Array of {x, y, z} points
*/
setTrackPath(path) {
this.trackPath = path;
this.totalPathLength = this._calculatePathLength();
this.progress = 0;
this.distance = 0;
this.updateCurrentPosition();
}
}
// Export for browser global scope
if (typeof window !== 'undefined') {
window.Train = Train;
window.TrainController = TrainController;
}