feat: add train driving system with 3D model and controls
- Added Train and TrainController classes in train.js - Created TrainRenderer for Three.js visualization - Integrated train controls into game.js - Updated index.html with train UI controls - Added train control styles to css/styles.css - Created test file for train module - Train can accelerate, brake, reverse and stop - Keyboard controls: W/↑ accelerate, S/↓ brake, R/D reverse, SPACE stop
This commit is contained in:
+225
@@ -0,0 +1,225 @@
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
Reference in New Issue
Block a user