Skip to content

Commit e6484cc

Browse files
committed
Hydrology and roads: rivers reach the sea, river barge trade, robust pathfinding
- Priority-flood depression filling before flow routing: every cell now drains monotonically to the rim or sea, so rivers always run from the highlands down instead of dead-ending in basins - River trade network: settlements on the same drainage system get barge routes traced along the actual river course; barges (boat meshes) carry goods faster than the road and beyond the reach of bandits; inspector labels cogs/barges/caravans distinctly - Binary-heap A* with a much higher search budget: mountain crossings get real switchback roads instead of silently falling back to invisible straight lines (0 fallbacks across test seeds, previously common) - Fallback tracks (if ever needed) and founded-village roads are now terrain-following, ground-tinted, and the road mesh rebuilds live Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJUuoQXWUkMk2YSxRM2MSp
1 parent 06ed1ee commit e6484cc

1 file changed

Lines changed: 179 additions & 57 deletions

File tree

index.html

Lines changed: 179 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,36 @@ <h3 id="insptitle">—</h3>
362362
if(maxh<400)for(let i=0;i<W.h.length;i++)W.h[i]*=400/maxh;
363363

364364
await progress('Carving the rivers…',0.2);
365-
/* --- hydrology: flow accumulation, carve rivers ----------------------- */
365+
/* --- hydrology: fill depressions, then flow accumulation, carve rivers --
366+
Priority-flood fill guarantees every cell drains monotonically to the
367+
map rim or the sea, so rivers always run from the highlands down. */
368+
{
369+
const visited=new Uint8Array(GRID*GRID);
370+
const heap=[];
371+
const hpush=(h,i)=>{heap.push([h,i]);let k=heap.length-1;
372+
while(k>0){const p=(k-1)>>1;if(heap[p][0]<=heap[k][0])break;const t=heap[p];heap[p]=heap[k];heap[k]=t;k=p;}};
373+
const hpop=()=>{const top=heap[0],last=heap.pop();
374+
if(heap.length){heap[0]=last;let k=0;
375+
for(;;){const l=k*2+1,r=l+1;let m=k;
376+
if(l<heap.length&&heap[l][0]<heap[m][0])m=l;
377+
if(r<heap.length&&heap[r][0]<heap[m][0])m=r;
378+
if(m===k)break;const t=heap[m];heap[m]=heap[k];heap[k]=t;k=m;}}
379+
return top;};
380+
for(let cx=0;cx<GRID;cx++){for(const cz of[0,GRID-1]){const i=cIdx(cx,cz);if(!visited[i]){visited[i]=1;hpush(W.h[i],i);}}}
381+
for(let cz=0;cz<GRID;cz++){for(const cx of[0,GRID-1]){const i=cIdx(cx,cz);if(!visited[i]){visited[i]=1;hpush(W.h[i],i);}}}
382+
while(heap.length){
383+
const[hh,c]=hpop();
384+
const cx=c%GRID,cz=(c/GRID)|0;
385+
for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++){
386+
if(!dx&&!dz)continue;const nx=cx+dx,nz=cz+dz;
387+
if(!inB(nx,nz))continue;const ni=cIdx(nx,nz);
388+
if(visited[ni])continue;
389+
visited[ni]=1;
390+
if(W.h[ni]<hh+0.05)W.h[ni]=hh+0.05; // fill pits just enough to drain
391+
hpush(W.h[ni],ni);
392+
}
393+
}
394+
}
366395
const order=[];for(let i=0;i<GRID*GRID;i++)order.push(i);
367396
order.sort((a,b)=>W.h[b]-W.h[a]);
368397
const down=new Int32Array(GRID*GRID).fill(-1);
@@ -397,6 +426,7 @@ <h3 id="insptitle">—</h3>
397426
if(path.length>4)W.riverPaths.push(path);
398427
}
399428
W.coastal=coastal;
429+
W.down=down; // drainage graph — reused for river barge routes
400430

401431
await progress('Seeding meadow and forest…',0.35);
402432
/* --- biomes ------------------------------------------------------------ */
@@ -494,25 +524,43 @@ <h3 id="insptitle">—</h3>
494524
return c;
495525
};
496526
function astar(s,t){
497-
const open=new Map(),g=new Map(),from=new Map();
498-
const hx=(i)=>{const ax=i%GRID,az=(i/GRID)|0,bx=t%GRID,bz=(t/GRID)|0;return Math.hypot(ax-bx,az-bz);};
499-
open.set(s,hx(s));g.set(s,0);
527+
// binary-heap A* — cheap enough to search the whole grid, so mountain
528+
// crossings get real switchback roads instead of falling back
529+
const g=new Float64Array(GRID*GRID).fill(Infinity);
530+
const from=new Int32Array(GRID*GRID).fill(-1);
531+
const closed=new Uint8Array(GRID*GRID);
532+
const heap=[];
533+
const hpush=(f,i)=>{heap.push([f,i]);let k=heap.length-1;
534+
while(k>0){const p=(k-1)>>1;if(heap[p][0]<=heap[k][0])break;const t2=heap[p];heap[p]=heap[k];heap[k]=t2;k=p;}};
535+
const hpop=()=>{const top=heap[0],last=heap.pop();
536+
if(heap.length){heap[0]=last;let k=0;
537+
for(;;){const l=k*2+1,r=l+1;let m=k;
538+
if(l<heap.length&&heap[l][0]<heap[m][0])m=l;
539+
if(r<heap.length&&heap[r][0]<heap[m][0])m=r;
540+
if(m===k)break;const t2=heap[m];heap[m]=heap[k];heap[k]=t2;k=m;}}
541+
return top;};
542+
const tx=t%GRID,tz=(t/GRID)|0;
543+
const hx=(i)=>Math.hypot(i%GRID-tx,((i/GRID)|0)-tz);
544+
g[s]=0;hpush(hx(s),s);
500545
let guard=0;
501-
while(open.size&&guard++<26000){
502-
let cur=-1,cf=1e18;for(const[k,f]of open)if(f<cf){cf=f;cur=k;}
546+
while(heap.length&&guard++<200000){
547+
const cur=hpop()[1];
548+
if(closed[cur])continue;
549+
closed[cur]=1;
503550
if(cur===t)break;
504-
open.delete(cur);
505-
const cx=cur%GRID,cz=(cur/GRID)|0,gc=g.get(cur);
551+
const cx=cur%GRID,cz=(cur/GRID)|0,gc=g[cur];
506552
for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++){
507553
if(!dx&&!dz)continue;const nx=cx+dx,nz=cz+dz;if(!inB(nx,nz))continue;
508-
const ni=cIdx(nx,nz),step=cost(cur,ni)*(dx&&dz?1.414:1);
554+
const ni=cIdx(nx,nz);
555+
if(closed[ni])continue;
556+
const step=cost(cur,ni)*(dx&&dz?1.414:1);
509557
if(step>1e8)continue;
510558
const ng=gc+step;
511-
if(ng<(g.get(ni)??1e18)){g.set(ni,ng);from.set(ni,cur);open.set(ni,ng+hx(ni));}
559+
if(ng<g[ni]){g[ni]=ng;from[ni]=cur;hpush(ng+hx(ni),ni);}
512560
}
513561
}
514-
if(!from.has(t)&&s!==t)return null;
515-
const path=[];let c=t;while(c!==undefined&&c!==s){path.push(c);c=from.get(c);}path.push(s);path.reverse();
562+
if(from[t]<0&&s!==t)return null;
563+
const path=[];let c=t;while(c>=0&&c!==s){path.push(c);c=from[c];}path.push(s);path.reverse();
516564
return path;
517565
}
518566
const connected=[0];
@@ -530,7 +578,17 @@ <h3 id="insptitle">—</h3>
530578
else W.roadCells[c]=1; // tint the ground under the king's roads
531579
}
532580
W.roads.push({a:i,b:bj,path,len});
533-
}else W.roads.push({a:i,b:bj,path:[{x:a.pos.x,z:a.pos.z},{x:b.pos.x,z:b.pos.z}],len:bd});
581+
}else{
582+
// no path found: lay a terrain-following track so the route is at least visible
583+
const path=[];const n=Math.max(2,Math.round(bd/25));
584+
for(let k=0;k<=n;k++){
585+
const x=lerp(a.pos.x,b.pos.x,k/n),z=lerp(a.pos.z,b.pos.z,k/n);
586+
path.push({x,z});
587+
const ci2=cIdx(toCell(x),toCell(z));
588+
if(!W.water[ci2])W.roadCells[ci2]=1;
589+
}
590+
W.roads.push({a:i,b:bj,path,len:bd});
591+
}
534592
connected.push(i);
535593
}
536594
// settlement-graph routing (BFS), route(a,b) => polyline
@@ -557,6 +615,27 @@ <h3 id="insptitle">—</h3>
557615
const res={poly,len};W.routes[key]=res;
558616
return res;
559617
}
618+
/* river barge routing: two settlements are river-linked if their drainage
619+
traces meet — same river, or tributary joining the main stem */
620+
function traceDown(ci){
621+
const seq=[];let c=ci,g=0;
622+
while(c>=0&&g++<3000){seq.push(c);if(W.h[c]<=SEA)break;c=W.down[c];}
623+
return seq;
624+
}
625+
function riverRoute(ai,bi){
626+
const A=W.settlements[ai],Bs=W.settlements[bi];
627+
if(A.rivCell==null||Bs.rivCell==null)return null;
628+
const sa=traceDown(A.rivCell),sb=traceDown(Bs.rivCell);
629+
const setB=new Map(sb.map((c,j)=>[c,j]));
630+
let mi=-1,mj=-1;
631+
for(let i=0;i<sa.length;i++){if(setB.has(sa[i])){mi=i;mj=setB.get(sa[i]);break;}}
632+
if(mi<0)return null;
633+
const cells=sa.slice(0,mi+1).concat(sb.slice(0,mj).reverse());
634+
if(cells.length<4)return null;
635+
const poly=cells.map(c=>({x:cellX(c%GRID),z:cellZ((c/GRID)|0)}));
636+
let len=0;for(let k=1;k<poly.length;k++)len+=dist2d(poly[k-1].x,poly[k-1].z,poly[k].x,poly[k].z);
637+
return {poly,len};
638+
}
560639

561640
/* ---------------- settlement layout: streets, lots, buildings ------------ */
562641
function layoutSettlement(s){
@@ -849,6 +928,43 @@ <h3 id="insptitle">—</h3>
849928
}
850929

851930
/* ---------------- terrain, water, roads ---------------------------------- */
931+
function chaikin(pts,passes){ // corner-cutting smoothing
932+
for(let k=0;k<passes;k++){
933+
const out=[pts[0]];
934+
for(let i=0;i<pts.length-1;i++){
935+
const a=pts[i],b=pts[i+1];
936+
out.push({x:a.x*0.75+b.x*0.25,z:a.z*0.75+b.z*0.25,w:(a.w||8)*0.75+(b.w||8)*0.25});
937+
out.push({x:a.x*0.25+b.x*0.75,z:a.z*0.25+b.z*0.75,w:(a.w||8)*0.25+(b.w||8)*0.75});
938+
}
939+
out.push(pts[pts.length-1]);
940+
pts=out;
941+
}
942+
return pts;
943+
}
944+
function ribbon(pts,acc,hw,lift){ // append triangles for a terrain-following strip
945+
for(let k=0;k<pts.length-1;k++){
946+
const a=pts[k],b=pts[k+1];
947+
const dx=b.x-a.x,dz=b.z-a.z,L=Math.hypot(dx,dz)||1;
948+
const wA=hw??(Math.max(10,a.w||10)/2),wB=hw??(Math.max(10,b.w||10)/2);
949+
const px=-dz/L,pz=dx/L;
950+
const ya=hAt(a.x,a.z)+lift,yb=hAt(b.x,b.z)+lift;
951+
acc.p.push(a.x+px*wA,ya,a.z+pz*wA, a.x-px*wA,ya,a.z-pz*wA, b.x-px*wB,yb,b.z-pz*wB,
952+
a.x+px*wA,ya,a.z+pz*wA, b.x-px*wB,yb,b.z-pz*wB, b.x+px*wB,yb,b.z+pz*wB);
953+
for(let i=0;i<6;i++)acc.n.push(0,1,0);
954+
}
955+
}
956+
function rebuildRoadMesh(){ // king's roads (wide) + settlement streets (narrow), one merged mesh
957+
if(G.roads){scene.remove(G.roads);G.roads.geometry.dispose();}
958+
const rd={p:[],n:[]};
959+
for(const r of W.roads)ribbon(chaikin(r.path,1),rd,5.2,0.9);
960+
for(const s of W.settlements)if(s.streets)for(const st of s.streets)ribbon(st,rd,2.6,0.8);
961+
const rrg=new THREE.BufferGeometry();
962+
rrg.setAttribute('position',new THREE.Float32BufferAttribute(rd.p,3));
963+
rrg.setAttribute('normal',new THREE.Float32BufferAttribute(rd.n,3));
964+
G.roads=new THREE.Mesh(rrg,G.roadMat||(G.roadMat=new THREE.MeshLambertMaterial({color:0x8f7c55,
965+
polygonOffset:true,polygonOffsetFactor:-2,polygonOffsetUnits:-2})));
966+
scene.add(G.roads);
967+
}
852968
function colorForVertex(i,sp,snowT,seed){
853969
const bio=W.biome[i],h=W.h[i];
854970
const nn=Math.sin(i*127.1+(seed%97))*43758.5453; const n=nn-Math.floor(nn); // stable patternless jitter
@@ -902,32 +1018,6 @@ <h3 id="insptitle">—</h3>
9021018
G.sea=new THREE.Mesh(sg,new THREE.MeshPhongMaterial({color:0x2e6285,transparent:true,opacity:0.92,shininess:90,specular:0x88aabb}));
9031019
G.sea.position.y=SEA+0.5;scene.add(G.sea);
9041020
}
905-
// river ribbons — Chaikin-smoothed so the water reads as one stroke
906-
function chaikin(pts,passes){
907-
for(let k=0;k<passes;k++){
908-
const out=[pts[0]];
909-
for(let i=0;i<pts.length-1;i++){
910-
const a=pts[i],b=pts[i+1];
911-
out.push({x:a.x*0.75+b.x*0.25,z:a.z*0.75+b.z*0.25,w:(a.w||8)*0.75+(b.w||8)*0.25});
912-
out.push({x:a.x*0.25+b.x*0.75,z:a.z*0.25+b.z*0.75,w:(a.w||8)*0.25+(b.w||8)*0.75});
913-
}
914-
out.push(pts[pts.length-1]);
915-
pts=out;
916-
}
917-
return pts;
918-
}
919-
const ribbon=(pts,acc,hw,lift)=>{ // append triangles for a smoothed strip
920-
for(let k=0;k<pts.length-1;k++){
921-
const a=pts[k],b=pts[k+1];
922-
const dx=b.x-a.x,dz=b.z-a.z,L=Math.hypot(dx,dz)||1;
923-
const wA=hw??(Math.max(10,a.w||10)/2),wB=hw??(Math.max(10,b.w||10)/2);
924-
const px=-dz/L,pz=dx/L;
925-
const ya=hAt(a.x,a.z)+lift,yb=hAt(b.x,b.z)+lift;
926-
acc.p.push(a.x+px*wA,ya,a.z+pz*wA, a.x-px*wA,ya,a.z-pz*wA, b.x-px*wB,yb,b.z-pz*wB,
927-
a.x+px*wA,ya,a.z+pz*wA, b.x-px*wB,yb,b.z-pz*wB, b.x+px*wB,yb,b.z+pz*wB);
928-
for(let i=0;i<6;i++)acc.n.push(0,1,0);
929-
}
930-
};
9311021
const riv={p:[],n:[]};
9321022
for(const path of W.riverPaths)ribbon(chaikin(path,2),riv,null,1.3);
9331023
const rg=new THREE.BufferGeometry();
@@ -936,16 +1026,7 @@ <h3 id="insptitle">—</h3>
9361026
G.rivers=new THREE.Mesh(rg,new THREE.MeshPhongMaterial({color:0x2e6ea0,transparent:true,opacity:0.95,shininess:110,specular:0x99bbcc,
9371027
polygonOffset:true,polygonOffsetFactor:-2,polygonOffsetUnits:-2}));
9381028
scene.add(G.rivers);
939-
// roads: king's roads (wide) + settlement streets (narrow), one merged mesh
940-
const rd={p:[],n:[]};
941-
for(const r of W.roads)ribbon(chaikin(r.path,1),rd,5.2,0.9);
942-
for(const s of W.settlements)if(s.streets)for(const st of s.streets)ribbon(st,rd,2.6,0.8);
943-
const rrg=new THREE.BufferGeometry();
944-
rrg.setAttribute('position',new THREE.Float32BufferAttribute(rd.p,3));
945-
rrg.setAttribute('normal',new THREE.Float32BufferAttribute(rd.n,3));
946-
G.roads=new THREE.Mesh(rrg,new THREE.MeshLambertMaterial({color:0x8f7c55,
947-
polygonOffset:true,polygonOffsetFactor:-2,polygonOffsetUnits:-2}));
948-
scene.add(G.roads);
1029+
rebuildRoadMesh();
9491030
// bridges
9501031
const bg=new THREE.BoxGeometry(1,1,1);
9511032
G.bridges=new THREE.InstancedMesh(bg,new THREE.MeshLambertMaterial({color:0x7d6748}),Math.max(1,W.bridges.length));
@@ -1169,9 +1250,9 @@ <h3 id="insptitle">—</h3>
11691250
sp.visible=false;scene.add(sp);G.caravanDots.push(sp);
11701251
}
11711252
}
1172-
// cogs
1253+
// cogs (sea traders) & river barges share the boat pool
11731254
G.cogs=[];
1174-
for(let i=0;i<8;i++){
1255+
for(let i=0;i<14;i++){
11751256
const grp=new THREE.Group();
11761257
const hull=new THREE.Mesh(new THREE.BoxGeometry(9,2.4,3.6),new THREE.MeshLambertMaterial({color:0x6e5233}));hull.position.y=1.4;
11771258
const sail=new THREE.Mesh(new THREE.PlaneGeometry(5,6),new THREE.MeshLambertMaterial({color:0xe4dcc4,side:THREE.DoubleSide}));sail.position.y=6;
@@ -1509,7 +1590,7 @@ <h3 id="insptitle">—</h3>
15091590
flyToBtn(fb,'Follow',()=>{cam.follow=a;cam.dist=260;});
15101591
}else if(pk.type==='caravan'){
15111592
const c=pk.c;
1512-
title.textContent='Caravan';
1593+
title.textContent=c.sea?'Trading cog':(c.river?'River barge':'Caravan');
15131594
sub.textContent=W.settlements[c.origin].name+' → '+W.settlements[c.dest].name;
15141595
h+=row('Cargo',c.good+' × '+Math.round(c.qty));
15151596
h+=row('Value',Math.round(c.value)+' gold');
@@ -1568,6 +1649,23 @@ <h3 id="insptitle">—</h3>
15681649
if(b===B.FARM)farm++;if(b===B.FOREST||b===B.PINE)forest++;if(b===B.ROCK)ore++;
15691650
}
15701651
s.res={farm,forest,ore:Math.min(ore,14),fish:(s.harbor?12:0)+(s.river?5:0)};
1652+
// nearest river cell, for barge landings
1653+
s.rivCell=null;
1654+
if(s.river){
1655+
let bd=1e9;
1656+
for(let dz=-6;dz<=6;dz++)for(let dx=-6;dx<=6;dx++){
1657+
const ci=s.cx+dx,cj=s.cz+dz;
1658+
if(!inB(ci,cj)||W.water[cIdx(ci,cj)]!==2)continue;
1659+
const d=dx*dx+dz*dz;if(d<bd){bd=d;s.rivCell=cIdx(ci,cj);}
1660+
}
1661+
}
1662+
}
1663+
// the river is a road that flows: barge routes between river-linked towns
1664+
W.riverPairs=[];
1665+
for(let i=0;i<W.settlements.length;i++)for(let j=i+1;j<W.settlements.length;j++){
1666+
if(!W.settlements[i].river||!W.settlements[j].river)continue;
1667+
const r=riverRoute(i,j);
1668+
if(r&&r.len>400)W.riverPairs.push({a:i,b:j,route:r});
15711669
}
15721670
}
15731671

@@ -1669,6 +1767,22 @@ <h3 id="insptitle">—</h3>
16691767
}
16701768
}
16711769
}
1770+
// river barges between river-linked settlements (faster and safer than the road)
1771+
if(W.riverPairs&&W.riverPairs.length&&W.caravans.filter(c=>c.river).length<5&&chance('sim',0.4*MOD.trade)){
1772+
const pr=pick('sim',W.riverPairs);
1773+
const downstream=chance('sim',0.5);
1774+
const oi=downstream?pr.a:pr.b,di=downstream?pr.b:pr.a;
1775+
const o=W.settlements[oi],d2=W.settlements[di];
1776+
if(o.infected/Math.max(1,o.pop)<=0.15&&d2.infected/Math.max(1,d2.pop)<=0.15&&!o.siegeBy&&!d2.siegeBy){
1777+
const g=GOODS.reduce((a,b)=>o.stores[a]>o.stores[b]?a:b);
1778+
if(o.stores[g]>25){
1779+
const qty=Math.min(o.stores[g]*0.3,70);o.stores[g]-=qty;
1780+
const poly=downstream?pr.route.poly:[...pr.route.poly].reverse();
1781+
W.caravans.push({origin:oi,dest:di,good:g,qty,value:qty*price(d2,g),poly,len:pr.route.len,
1782+
departDay:day(),arriveDay:day()+pr.route.len/1500,robbed:false,sea:false,river:true,proxy:null});
1783+
}
1784+
}
1785+
}
16721786
// cogs between harbors
16731787
const harbors=W.settlements.map((s,i)=>s.harbor?i:-1).filter(i=>i>=0);
16741788
if(harbors.length>=2&&W.caravans.filter(c=>c.sea).length<6&&chance('sim',0.3*MOD.trade)){
@@ -2351,7 +2465,7 @@ <h3 id="insptitle">—</h3>
23512465
}
23522466
// ambush caravans passing near (position computed in sim time, not render time)
23532467
for(const c of W.caravans){
2354-
if(c.sea||c.robbed)continue;
2468+
if(c.sea||c.river||c.robbed)continue; // barges are beyond the outlaws' reach
23552469
const f=(day()-c.departDay)/Math.max(0.01,c.arriveDay-c.departDay);
23562470
const p=polyPos(c.poly,f);
23572471
if(!p)continue;
@@ -2574,9 +2688,17 @@ <h3 id="insptitle">—</h3>
25742688
W.settlements.push(s);
25752689
const si=W.settlements.length-1;W.adj[si]=[];
25762690
let bj=0,bd=1e18;W.settlements.forEach((o,oi)=>{if(oi!==si){const d=dist2d(p.x,p.z,o.pos.x,o.pos.z);if(d<bd){bd=d;bj=oi;}}});
2577-
const path=[{x:p.x,z:p.z},{x:W.settlements[bj].pos.x,z:W.settlements[bj].pos.z}];
2691+
const tgt=W.settlements[bj].pos;
2692+
const path=[];const nseg=Math.max(2,Math.round(bd/25));
2693+
for(let k=0;k<=nseg;k++){
2694+
const x=lerp(p.x,tgt.x,k/nseg),z=lerp(p.z,tgt.z,k/nseg);
2695+
path.push({x,z});
2696+
const rc=cIdx(toCell(x),toCell(z));
2697+
if(!W.water[rc])W.roadCells[rc]=1;
2698+
}
25782699
W.roads.push({a:si,b:bj,path,len:bd});W.routes={};
25792700
W.adj[si].push({to:bj,ri:W.roads.length-1});W.adj[bj].push({to:si,ri:W.roads.length-1});
2701+
rebuildRoadMesh();lastSeasonKey=''; // show the new track and its ground tint
25802702
for(let i=0;i<10;i++)addBuildingLive(s);
25812703
const sp=makeTextSprite(s.name,1.6);sp.position.set(p.x,s.pos.y+90,p.z);scene.add(sp);G.labels.push(sp);
25822704
const m=new THREE.Mesh(new THREE.SphereGeometry(60,8,6),new THREE.MeshBasicMaterial({visible:false}));
@@ -3126,15 +3248,15 @@ <h3 id="insptitle">—</h3>
31263248
const f=(simDay-c.departDay)/Math.max(0.01,c.arriveDay-c.departDay);
31273249
const p=polyPos(c.poly,f);
31283250
if(!p)continue;
3129-
const y=c.sea?SEA+1:hAt(p.x,p.z)+1.0; // ride on the road surface, not inside it
3251+
const y=c.sea?SEA+1:(c.river?hAt(p.x,p.z)+1.2:hAt(p.x,p.z)+1.0); // road surface or water line
31303252
c._rpos={x:p.x,y:y+8,z:p.z};
31313253
if(!c.proxy){
31323254
c.proxy=new THREE.Mesh(new THREE.SphereGeometry(16,6,5),new THREE.MeshBasicMaterial({visible:false}));
31333255
c.proxy.userData.pick={type:'caravan',c};scene.add(c.proxy);
31343256
}
31353257
c.proxy.position.set(p.x,y+6,p.z);
3136-
if(c.sea){
3137-
if(cogI<G.cogs.length){const g=G.cogs[cogI++];g.visible=true;g.position.set(p.x,y,p.z);g.rotation.y=-(p.dir||0);g.scale.setScalar(Math.min(unitScale,3));}
3258+
if(c.sea||c.river){
3259+
if(cogI<G.cogs.length){const g=G.cogs[cogI++];g.visible=true;g.position.set(p.x,y,p.z);g.rotation.y=-(p.dir||0);g.scale.setScalar(Math.min(unitScale,c.river?2:3));}
31383260
}else if(ci<48){
31393261
setInst(G.caravans,ci++,p.x,y,p.z,-(p.dir||0),unitScale,unitScale,unitScale);
31403262
}

0 commit comments

Comments
 (0)