| 10332 | of the map rather than by rank or by index, so the route is a lap of the |
| 10333 | realm and not a zigzag across it — and closed, so the tour never ends. */ |
| 10334 | function tourRoute(){ |
| 10335 | const live=(OPEN?OPEN.list:W.quarters).filter(x=>(OPEN?x.town:x.island)); |
| 10336 | if(!live.length)return null; |
| 10337 | let cx=0,cz=0; |
| 10338 | for(const x of live){ cx+=x.x; cz+=x.z; } |
| 10339 | cx/=live.length; cz/=live.length; |
| 10340 | const ring=[...live].sort((a,b)=> |
| 10341 | Math.atan2(a.z-cz,a.x-cx)-Math.atan2(b.z-cz,b.x-cx)); |
| 10342 | const pts=[]; |
| 10343 | for(const x of ring){ |
| 10344 | const R=spec(Math.max(1,x.level||1)).radius; |
| 10345 | /* Just off the rim and just over the roofline. A route through the middle |
| 10346 | of a town at spire height is a route through the spires; one high enough |
| 10347 | to clear everything is a map, and you can already read the map from |
| 10348 | outside. Skimming the outer edge is the one altitude where the place has |
| 10349 | a scale — you pass the towers rather than looking down on them. */ |
| 10350 | const off=R*0.95+2; |
| 10351 | const a=Math.atan2(x.z-cz,x.x-cx); |
| 10352 | pts.push(new THREE.Vector3(x.x+Math.cos(a)*off, |
| 10353 | (x.baseY||0)+R*0.5+4.5, |
| 10354 | x.z+Math.sin(a)*off)); |
| 10355 | } |
| 10356 | /* One waypoint is not a loop and two is a line you fly back and forth along; |
| 10357 | both come out as a degenerate curve, so pad them into a circle. */ |
| 10358 | if(pts.length<3){ |
| 10359 | const p=pts[0], r=Math.max(12,Math.hypot(p.x-cx,p.z-cz)); |
| 10360 | pts.length=0; |
| 10361 | for(let i=0;i<5;i++){ const a=i/5*TAU; |
| 10362 | pts.push(new THREE.Vector3(cx+Math.cos(a)*r,p?p.y:12,cz+Math.sin(a)*r)); } |
| 10363 | } |
| 10364 | const curve=new THREE.CatmullRomCurve3(pts,true,'catmullrom',0.4); |
| 10365 | return {curve,len:curve.getLength()}; |
| 10366 | } |
| 10367 | |
| 10368 | function possess(b,host){ |
| 10369 | if(POV.bird)return; |