fix: smooth rail curves and deterministic track generation
- Use bezier curve paths instead of polylines for rails to eliminate jaggies on sharp turns - Sample rail waypoints densely (every 2px) with gentle control points (0.33) - Use longer lookahead distance (8px) for smoother perpendicular angle calculation - Implement seeded random number generator for consistent tracks per route - Each route generates a unique but deterministic track layout
This commit is contained in:
parent
46d4af2bda
commit
4f79c08d73
|
|
@ -7,8 +7,8 @@ import type { Landmark } from '../../lib/landmarks'
|
|||
interface RailroadTrackPathProps {
|
||||
tiesAndRails: {
|
||||
ties: Array<{ x1: number; y1: number; x2: number; y2: number }>
|
||||
leftRailPoints: string[]
|
||||
rightRailPoints: string[]
|
||||
leftRailPath: string
|
||||
rightRailPath: string
|
||||
} | null
|
||||
referencePath: string
|
||||
pathRef: React.RefObject<SVGPathElement>
|
||||
|
|
@ -51,24 +51,26 @@ export const RailroadTrackPath = memo(({
|
|||
))}
|
||||
|
||||
{/* Left rail */}
|
||||
{tiesAndRails && tiesAndRails.leftRailPoints.length > 1 && (
|
||||
<polyline
|
||||
points={tiesAndRails.leftRailPoints.join(' ')}
|
||||
{tiesAndRails && tiesAndRails.leftRailPath && (
|
||||
<path
|
||||
d={tiesAndRails.leftRailPath}
|
||||
fill="none"
|
||||
stroke="#C0C0C0"
|
||||
strokeWidth="5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Right rail */}
|
||||
{tiesAndRails && tiesAndRails.rightRailPoints.length > 1 && (
|
||||
<polyline
|
||||
points={tiesAndRails.rightRailPoints.join(' ')}
|
||||
{tiesAndRails && tiesAndRails.rightRailPath && (
|
||||
<path
|
||||
d={tiesAndRails.rightRailPath}
|
||||
fill="none"
|
||||
stroke="#C0C0C0"
|
||||
strokeWidth="5"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
/>
|
||||
)}
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ export interface TrackElements {
|
|||
ballastPath: string
|
||||
referencePath: string
|
||||
ties: Array<{ x1: number; y1: number; x2: number; y2: number }>
|
||||
leftRailPoints: string[]
|
||||
rightRailPoints: string[]
|
||||
leftRailPath: string
|
||||
rightRailPath: string
|
||||
}
|
||||
|
||||
export class RailroadTrackGenerator {
|
||||
|
|
@ -43,6 +43,14 @@ export class RailroadTrackGenerator {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeded random number generator for deterministic randomness
|
||||
*/
|
||||
private seededRandom(seed: number): number {
|
||||
const x = Math.sin(seed) * 10000
|
||||
return x - Math.floor(x)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate waypoints for track with controlled randomness
|
||||
* Based on route number for variety across different routes
|
||||
|
|
@ -60,18 +68,17 @@ export class RailroadTrackGenerator {
|
|||
{ x: 780, y: 300 } // Enter right tunnel center
|
||||
]
|
||||
|
||||
// Add controlled randomness for variety (but keep start/end fixed)
|
||||
// Use route number as seed for consistent randomness per route
|
||||
const seed = routeNumber * 123.456
|
||||
|
||||
// Add deterministic randomness based on route number (but keep start/end fixed)
|
||||
return baseWaypoints.map((point, index) => {
|
||||
if (index === 0 || index === baseWaypoints.length - 1) {
|
||||
return point // Keep start/end points fixed
|
||||
}
|
||||
|
||||
// Use deterministic randomness based on route and index
|
||||
const randomX = Math.sin(seed + index * 1.1) * 30
|
||||
const randomY = Math.cos(seed + index * 1.3) * 40
|
||||
// Use seeded randomness for consistent track per route
|
||||
const seed1 = routeNumber * 12.9898 + index * 78.233
|
||||
const seed2 = routeNumber * 43.789 + index * 67.123
|
||||
const randomX = (this.seededRandom(seed1) - 0.5) * 60 // ±30 pixels
|
||||
const randomY = (this.seededRandom(seed2) - 0.5) * 80 // ±40 pixels
|
||||
|
||||
return {
|
||||
x: point.x + randomX,
|
||||
|
|
@ -107,14 +114,42 @@ export class RailroadTrackGenerator {
|
|||
return pathData
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate gentle curves through densely sampled waypoints
|
||||
* Uses very gentle control points to avoid wobbles in straight sections
|
||||
*/
|
||||
private generateGentlePath(waypoints: Waypoint[]): string {
|
||||
if (waypoints.length < 2) return ''
|
||||
|
||||
let pathData = `M ${waypoints[0].x} ${waypoints[0].y}`
|
||||
|
||||
for (let i = 1; i < waypoints.length; i++) {
|
||||
const current = waypoints[i]
|
||||
const previous = waypoints[i - 1]
|
||||
|
||||
// Use extremely gentle control points for very dense sampling
|
||||
const dx = current.x - previous.x
|
||||
const dy = current.y - previous.y
|
||||
|
||||
const cp1x = previous.x + dx * 0.33
|
||||
const cp1y = previous.y + dy * 0.33
|
||||
const cp2x = current.x - dx * 0.33
|
||||
const cp2y = current.y - dy * 0.33
|
||||
|
||||
pathData += ` C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${current.x} ${current.y}`
|
||||
}
|
||||
|
||||
return pathData
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate railroad ties and rails along the path
|
||||
* This requires an SVG path element to measure
|
||||
*/
|
||||
generateTiesAndRails(pathElement: SVGPathElement): {
|
||||
ties: Array<{ x1: number; y1: number; x2: number; y2: number }>
|
||||
leftRailPoints: string[]
|
||||
rightRailPoints: string[]
|
||||
leftRailPath: string
|
||||
rightRailPath: string
|
||||
} {
|
||||
const pathLength = pathElement.getTotalLength()
|
||||
const tieSpacing = 12 // Distance between ties in pixels
|
||||
|
|
@ -122,8 +157,6 @@ export class RailroadTrackGenerator {
|
|||
const tieCount = Math.floor(pathLength / tieSpacing)
|
||||
|
||||
const ties: Array<{ x1: number; y1: number; x2: number; y2: number }> = []
|
||||
const leftRailPoints: string[] = []
|
||||
const rightRailPoints: string[] = []
|
||||
|
||||
// Generate ties at normal spacing
|
||||
for (let i = 0; i < tieCount; i++) {
|
||||
|
|
@ -146,33 +179,39 @@ export class RailroadTrackGenerator {
|
|||
ties.push({ x1: leftX, y1: leftY, x2: rightX, y2: rightY })
|
||||
}
|
||||
|
||||
// Generate rail points with much higher density for smooth curves
|
||||
// Use 3px spacing instead of 12px tie spacing to eliminate jaggies on curves
|
||||
const railSpacing = 3
|
||||
const railPointCount = Math.floor(pathLength / railSpacing)
|
||||
// Generate rail paths as smooth curves (not polylines)
|
||||
// Sample points along the path and create offset waypoints
|
||||
const railSampling = 2 // Sample every 2 pixels for waypoints (very dense sampling for smooth curves)
|
||||
const sampleCount = Math.floor(pathLength / railSampling)
|
||||
|
||||
for (let i = 0; i < railPointCount; i++) {
|
||||
const distance = i * railSpacing
|
||||
const leftRailWaypoints: Waypoint[] = []
|
||||
const rightRailWaypoints: Waypoint[] = []
|
||||
|
||||
for (let i = 0; i <= sampleCount; i++) {
|
||||
const distance = Math.min(i * railSampling, pathLength)
|
||||
const point = pathElement.getPointAtLength(distance)
|
||||
|
||||
// Calculate perpendicular angle for rail orientation
|
||||
const nextDistance = Math.min(distance + 2, pathLength)
|
||||
// Calculate perpendicular angle with longer lookahead for smoother curves
|
||||
const nextDistance = Math.min(distance + 8, pathLength)
|
||||
const nextPoint = pathElement.getPointAtLength(nextDistance)
|
||||
const angle = Math.atan2(nextPoint.y - point.y, nextPoint.x - point.x)
|
||||
const perpAngle = angle + Math.PI / 2
|
||||
|
||||
// Calculate rail positions
|
||||
// Calculate offset positions for rails
|
||||
const leftX = point.x + Math.cos(perpAngle) * gaugeWidth
|
||||
const leftY = point.y + Math.sin(perpAngle) * gaugeWidth
|
||||
const rightX = point.x - Math.cos(perpAngle) * gaugeWidth
|
||||
const rightY = point.y - Math.sin(perpAngle) * gaugeWidth
|
||||
|
||||
// Collect points for rails
|
||||
leftRailPoints.push(`${leftX},${leftY}`)
|
||||
rightRailPoints.push(`${rightX},${rightY}`)
|
||||
leftRailWaypoints.push({ x: leftX, y: leftY })
|
||||
rightRailWaypoints.push({ x: rightX, y: rightY })
|
||||
}
|
||||
|
||||
return { ties, leftRailPoints, rightRailPoints }
|
||||
// Generate smooth curved paths through the rail waypoints with gentle control points
|
||||
const leftRailPath = this.generateGentlePath(leftRailWaypoints)
|
||||
const rightRailPath = this.generateGentlePath(rightRailWaypoints)
|
||||
|
||||
return { ties, leftRailPath, rightRailPath }
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
Loading…
Reference in New Issue