@@ -7,6 +7,7 @@ import { useStore } from '@/core/zustand';
77import {
88 INFRASTRUCTURE_CATEGORIES ,
99 type InfrastructureCategory ,
10+ type InfrastructureSplinesBlock ,
1011 type ParsedInfrastructure ,
1112 SPLINE_KINDS ,
1213 type SplineKind ,
@@ -336,6 +337,81 @@ function paintTileBuildings(
336337 ctx . globalAlpha = 1 ;
337338}
338339
340+ /**
341+ * Appends a single polyline to `path`. When `step === 1` and the block
342+ * has Hermite tangents (`block.tangentsXY`) the segments are emitted
343+ * as `bezierCurveTo` so curved track / belt sections render as actual
344+ * curves. Otherwise a straight `lineTo` chain is used, with sub-pixel
345+ * points skipped to keep the tile-paint fast at low zoom.
346+ *
347+ * Shared between `paintTileSplines` (tile-batched main render) and
348+ * `drawHighlight` (single-polyline hover stroke) so both paths use the
349+ * same curve geometry — keeping the hover accent on top of the curve
350+ * rather than collapsing it to chords.
351+ */
352+ function appendPolylineToPath (
353+ path : Path2D ,
354+ block : InfrastructureSplinesBlock ,
355+ polylineIndex : number ,
356+ a : AffineGameToContainer ,
357+ step : number ,
358+ ) : void {
359+ const start = block . offsets [ polylineIndex ] ;
360+ const end = block . offsets [ polylineIndex + 1 ] ;
361+ if ( end - start < 2 ) return ;
362+
363+ const { pointsXY, tangentsXY } = block ;
364+ const sx = a . ox + pointsXY [ start * 2 ] * a . ax ;
365+ const sy = a . oy + pointsXY [ start * 2 + 1 ] * a . by ;
366+ path . moveTo ( sx , sy ) ;
367+
368+ if ( step === 1 && tangentsXY != null ) {
369+ const t = tangentsXY ;
370+ let p0x = sx ;
371+ let p0y = sy ;
372+ for ( let j = start + 1 ; j < end ; j ++ ) {
373+ const p1x = a . ox + pointsXY [ j * 2 ] * a . ax ;
374+ const p1y = a . oy + pointsXY [ j * 2 + 1 ] * a . by ;
375+ const leaveX = ( t [ ( j - 1 ) * 4 + 2 ] * a . ax ) / 3 ;
376+ const leaveY = ( t [ ( j - 1 ) * 4 + 3 ] * a . by ) / 3 ;
377+ const arriveX = ( t [ j * 4 ] * a . ax ) / 3 ;
378+ const arriveY = ( t [ j * 4 + 1 ] * a . by ) / 3 ;
379+ path . bezierCurveTo (
380+ p0x + leaveX ,
381+ p0y + leaveY ,
382+ p1x - arriveX ,
383+ p1y - arriveY ,
384+ p1x ,
385+ p1y ,
386+ ) ;
387+ p0x = p1x ;
388+ p0y = p1y ;
389+ }
390+ return ;
391+ }
392+
393+ let lastDX = sx ;
394+ let lastDY = sy ;
395+ const lastIdx = end - 1 ;
396+ for ( let j = start + step ; j < lastIdx ; j += step ) {
397+ const px = a . ox + pointsXY [ j * 2 ] * a . ax ;
398+ const py = a . oy + pointsXY [ j * 2 + 1 ] * a . by ;
399+ const dx = px - lastDX ;
400+ const dy = py - lastDY ;
401+ if ( dx * dx + dy * dy < 1 ) continue ;
402+ path . lineTo ( px , py ) ;
403+ lastDX = px ;
404+ lastDY = py ;
405+ }
406+ const lx = a . ox + pointsXY [ lastIdx * 2 ] * a . ax ;
407+ const ly = a . oy + pointsXY [ lastIdx * 2 + 1 ] * a . by ;
408+ const ldx = lx - lastDX ;
409+ const ldy = ly - lastDY ;
410+ if ( ldx * ldx + ldy * ldy >= 1 || end - start === 2 ) {
411+ path . lineTo ( lx , ly ) ;
412+ }
413+ }
414+
339415function paintTileSplines (
340416 ctx : CanvasRenderingContext2D ,
341417 infra : ParsedInfrastructure ,
@@ -353,6 +429,7 @@ function paintTileSplines(
353429 hyper : 1 ,
354430 rail : 1.8 ,
355431 power : 0.75 ,
432+ vehicle : 1 ,
356433 } ;
357434
358435 const step = splineStepForZoom ( zoom ) ;
@@ -369,67 +446,13 @@ function paintTileSplines(
369446 if ( ! SPLINE_KINDS . includes ( block . kind ) ) continue ;
370447 if ( ( state . splineVisibility ?. [ block . kind ] ?? true ) === false ) continue ;
371448
372- const start = block . offsets [ polyIdx ] ;
373- const end = block . offsets [ polyIdx + 1 ] ;
374- if ( end - start < 2 ) continue ;
375-
376449 let path = pathsByBlock . get ( blockIdx ) ;
377450 if ( ! path ) {
378451 path = new Path2D ( ) ;
379452 pathsByBlock . set ( blockIdx , path ) ;
380453 }
381454
382- const { pointsXY, tangentsXY } = block ;
383- const useBezier = step === 1 && tangentsXY != null ;
384- const sx = a . ox + pointsXY [ start * 2 ] * a . ax ;
385- const sy = a . oy + pointsXY [ start * 2 + 1 ] * a . by ;
386- path . moveTo ( sx , sy ) ;
387-
388- if ( useBezier ) {
389- const t = tangentsXY ;
390- let p0x = sx ;
391- let p0y = sy ;
392- for ( let j = start + 1 ; j < end ; j ++ ) {
393- const p1x = a . ox + pointsXY [ j * 2 ] * a . ax ;
394- const p1y = a . oy + pointsXY [ j * 2 + 1 ] * a . by ;
395- const leaveX = ( t [ ( j - 1 ) * 4 + 2 ] * a . ax ) / 3 ;
396- const leaveY = ( t [ ( j - 1 ) * 4 + 3 ] * a . by ) / 3 ;
397- const arriveX = ( t [ j * 4 ] * a . ax ) / 3 ;
398- const arriveY = ( t [ j * 4 + 1 ] * a . by ) / 3 ;
399- path . bezierCurveTo (
400- p0x + leaveX ,
401- p0y + leaveY ,
402- p1x - arriveX ,
403- p1y - arriveY ,
404- p1x ,
405- p1y ,
406- ) ;
407- p0x = p1x ;
408- p0y = p1y ;
409- }
410- continue ;
411- }
412-
413- let lastDX = sx ;
414- let lastDY = sy ;
415- const lastIdx = end - 1 ;
416- for ( let j = start + step ; j < lastIdx ; j += step ) {
417- const px = a . ox + pointsXY [ j * 2 ] * a . ax ;
418- const py = a . oy + pointsXY [ j * 2 + 1 ] * a . by ;
419- const dx = px - lastDX ;
420- const dy = py - lastDY ;
421- if ( dx * dx + dy * dy < 1 ) continue ;
422- path . lineTo ( px , py ) ;
423- lastDX = px ;
424- lastDY = py ;
425- }
426- const lx = a . ox + pointsXY [ lastIdx * 2 ] * a . ax ;
427- const ly = a . oy + pointsXY [ lastIdx * 2 + 1 ] * a . by ;
428- const ldx = lx - lastDX ;
429- const ldy = ly - lastDY ;
430- if ( ldx * ldx + ldy * ldy >= 1 || end - start === 2 ) {
431- path . lineTo ( lx , ly ) ;
432- }
455+ appendPolylineToPath ( path , block , polyIdx , a , step ) ;
433456 }
434457
435458 ctx . lineCap = 'round' ;
@@ -489,21 +512,11 @@ function drawHighlight(
489512 ctx . restore ( ) ;
490513 return ;
491514 }
492- const start = block . offsets [ hit . polylineIndex ] ;
493- const end = block . offsets [ hit . polylineIndex + 1 ] ;
494- if ( end - start < 2 ) {
495- ctx . restore ( ) ;
496- return ;
497- }
498515 const path = new Path2D ( ) ;
499- const sx = a . ox + block . pointsXY [ start * 2 ] * a . ax ;
500- const sy = a . oy + block . pointsXY [ start * 2 + 1 ] * a . by ;
501- path . moveTo ( sx , sy ) ;
502- for ( let j = start + 1 ; j < end ; j ++ ) {
503- const px = a . ox + block . pointsXY [ j * 2 ] * a . ax ;
504- const py = a . oy + block . pointsXY [ j * 2 + 1 ] * a . by ;
505- path . lineTo ( px , py ) ;
506- }
516+ // step=1 forces the full point sequence; combined with tangentsXY it
517+ // selects the bezier branch so the hover accent traces the curve
518+ // instead of collapsing to straight chords on top of it.
519+ appendPolylineToPath ( path , block , hit . polylineIndex , a , 1 ) ;
507520
508521 ctx . strokeStyle = 'rgba(255, 255, 255, 0.85)' ;
509522 ctx . lineWidth = 6 ;
0 commit comments