Draw track preview with native cubic curves

#4
by TiGa-RCE - opened
Files changed (2) hide show
  1. RELEASE_MANIFEST.json +1 -1
  2. track-map.js +19 -3
RELEASE_MANIFEST.json CHANGED
@@ -83,7 +83,7 @@
83
  "star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
84
  "steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
85
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
86
- "track-map.js": "547b737d123d9479fc166ddc564568c0ff4b2099200c78e36bb46ec461bc754c",
87
  "training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
88
  "vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
89
  "vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
 
83
  "star_check.py": "5c17080957b81ffccf0de8314635b471562e55c92c97a1207d9034a99279b2c3",
84
  "steam_leaderboard.py": "d76397c39cae58f636ddf9a9be1a46ae3d77fefd6b7269feac73d34d97165481",
85
  "track-library.js": "351ec4c026b49420ce963f4310cb000d9e987fd28e6dac5ecd0176509340f6e6",
86
+ "track-map.js": "a5e76598d5988c0446ad3b5796b601467cf9423e6daddf21753258b6eee0c195",
87
  "training_config.py": "6eb7282bbee3b3a6273bddc29c949d9904067414c26e501d815012f033173a1c",
88
  "vendor/gradio-client/LICENSE": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4",
89
  "vendor/gradio-client/dist/index.min.js": "fdd776db8633354f118fe2908c980592283968737e6b91dc3aa86d648da5c3fd",
track-map.js CHANGED
@@ -33,6 +33,20 @@
33
  case 3:return [x+size,y+offset];
34
  }
35
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  async function decode(file){
37
  if(!file||file.size<1||file.size>1048576)throw Error('Choose a .track file under 1 MB');
38
  if(typeof DecompressionStream!=='function')throw Error('This browser cannot preview gzip tracks');
@@ -89,9 +103,11 @@
89
  context.strokeStyle='#d7e8f2';context.lineWidth=Math.max(1,size*.018);
90
  context.stroke();
91
  if(Array.isArray(tile.connections)&&tile.connections.length>=2){
92
- const a=connectionPoint(tile.connections[0],x,y,size),z=connectionPoint(tile.connections[1],x,y,size);
 
 
93
  context.beginPath();context.moveTo(...a);
94
- context.quadraticCurveTo(x+size/2,y+size/2,...z);
95
  context.strokeStyle='#f3a967';context.lineWidth=Math.max(3,size*.055);
96
  context.lineCap='round';context.stroke();
97
  }
@@ -125,7 +141,7 @@
125
  status.textContent=source+' · '+track.tiles.length+' pieces · green dot marks the saved starting point';
126
  legend.textContent=NAMES.filter((_,i)=>counts[i]).map((name,index)=>{
127
  const i=NAMES.indexOf(name);return name+' '+counts[i];
128
- }).join(' · ')+' · Orange line shows the approximate route; collision shapes and car positions are not shown.';
129
  }catch(error){
130
  if(current!==version)return;
131
  renderEmpty();status.textContent='Map unavailable: '+error.message;legend.textContent='Training selection is unaffected.';
 
33
  case 3:return [x+size,y+offset];
34
  }
35
  }
36
+ function connectionControl(own,other,point,target){
37
+ // Native TrackUtils.GetPathCurve (random_track.rs, b90b8af): each
38
+ // connection's inward normal sets its cubic handle, adjusted by port type.
39
+ // At the exit this is the next tile's negated entry normal, so the same
40
+ // per-tile construction also preserves the curve when endpoints reverse.
41
+ const [nx,ny]=[[0,1],[0,-1],[1,0],[-1,0]][own.side];
42
+ const dx=target[0]-point[0],dy=target[1]-point[1];
43
+ const angle=own.type===1?Math.PI/4:own.type===3?Math.atan2(128,384):own.type===4?Math.atan2(384,128):0;
44
+ const rotation=Math.sign(nx*dy-ny*dx)*angle;
45
+ const c=Math.cos(rotation),s=Math.sin(rotation);
46
+ const smoothness=[0,2,100].includes(own.type)&&[1,3,4].includes(other.type)?0.2:0.5;
47
+ const reach=Math.hypot(dx,dy)*smoothness;
48
+ return [point[0]+(nx*c-ny*s)*reach,point[1]+(nx*s+ny*c)*reach];
49
+ }
50
  async function decode(file){
51
  if(!file||file.size<1||file.size>1048576)throw Error('Choose a .track file under 1 MB');
52
  if(typeof DecompressionStream!=='function')throw Error('This browser cannot preview gzip tracks');
 
103
  context.strokeStyle='#d7e8f2';context.lineWidth=Math.max(1,size*.018);
104
  context.stroke();
105
  if(Array.isArray(tile.connections)&&tile.connections.length>=2){
106
+ const [entry,exit]=tile.connections;
107
+ const a=connectionPoint(entry,x,y,size),z=connectionPoint(exit,x,y,size);
108
+ const p1=connectionControl(entry,exit,a,z),p2=connectionControl(exit,entry,z,a);
109
  context.beginPath();context.moveTo(...a);
110
+ context.bezierCurveTo(...p1,...p2,...z);
111
  context.strokeStyle='#f3a967';context.lineWidth=Math.max(3,size*.055);
112
  context.lineCap='round';context.stroke();
113
  }
 
141
  status.textContent=source+' · '+track.tiles.length+' pieces · green dot marks the saved starting point';
142
  legend.textContent=NAMES.filter((_,i)=>counts[i]).map((name,index)=>{
143
  const i=NAMES.indexOf(name);return name+' '+counts[i];
144
+ }).join(' · ')+' · Orange line follows the track centerline; collision shapes and car positions are not shown.';
145
  }catch(error){
146
  if(current!==version)return;
147
  renderEmpty();status.textContent='Map unavailable: '+error.message;legend.textContent='Training selection is unaffected.';