Show folder paths on blade signs at street corners

Blades named the file they were attached to. They now act as street
signs: for each corner of a folder's block, the building nearest that
corner gets a green blade on each wall facing the corner's two streets,
near the corner end, reading the folder path (shortened from the left
when long; the repository name at the root). Neighbouring blocks each
sign their own corner, so an intersection shows a different folder on
every side. Courtyard walls set back from the street are skipped, and
blades still shorten or disappear when a neighbour is too close. File
names remain on the flat wall and roof signs.
This commit is contained in:
Andy Dixon
2026-06-13 09:14:13 +01:00
parent c31b0b3d39
commit 1e11e4a650
4 changed files with 102 additions and 35 deletions
+12 -3
View File
@@ -1,5 +1,5 @@
import { LandscapeRenderer, KIND } from './renderer.js';
import { layoutCity, squarifiedLayout, spatialIndex, pickRay, stepCamera, collide, blockAt, groundHit, flatQuad, wallSigns, bladeSigns, facadeQuad, heading, tourStops, encodePlace, decodePlace, joystick, MOVE } from './city.js';
import { layoutCity, squarifiedLayout, spatialIndex, pickRay, stepCamera, collide, blockAt, groundHit, flatQuad, wallSigns, folderBlades, folderLabel, facadeQuad, heading, tourStops, encodePlace, decodePlace, joystick, MOVE } from './city.js';
import { LabelAtlas } from './labels.js';
import { apiFetch, progressEvents } from './api.mjs';
@@ -46,7 +46,7 @@ let searchHits=[];
let expandedCoverageLayer=null;
let coverageActiveIndex=-1;
let cameraAnimation=null;
let cityModel=null,cityEntries=[],cityIndex=null,cityFitted=false,cityDistricts=[];
let cityModel=null,cityEntries=[],cityIndex=null,cityFitted=false,cityDistricts=[],cityFolders=[];
let currentSnapshot=null;
let currentName='';
let currentMetric='lines';
@@ -128,6 +128,10 @@ function computeCity(){
instances.push({...building,color:item.color,atlas:[item.x/1000,item.y/680,item.w/1000,item.h/680],kind:KIND.building,id:item.id,lit:Math.min(.85,.06+Math.sqrt(symbols)/9)});
}
cityIndex=spatialIndex(cityEntries);
// Each folder's own buildings (not its subfolders'), for street-corner signs.
const byDirectory=new Map();
for(const b of cityEntries){const list=byDirectory.get(b.file.directory||'');if(list)list.push(b);else byDirectory.set(b.file.directory||'',[b]);}
cityFolders=cityModel.blocks.map(block=>({block,buildings:byDirectory.get(block.node.path)||[],label:folderLabel(block.node.path,currentName)})).filter(folder=>folder.buildings.length);
const tally=node=>{let files=node.files.length,lines=node.files.reduce((n,f)=>n+(f.lines||0),0);for(const child of node.children.values()){const t=tally(child);files+=t.files;lines+=t.lines;}return{files,lines};};
cityDistricts=cityModel.blocks.filter(block=>block.depth===1).map(block=>({block,...tally(block.node),top:Math.max(10,...[...cityIndex.near(block.x+block.w/2,block.y+block.h/2,Math.hypot(block.w,block.h)/2)].filter(b=>b.x>=block.x&&b.x+b.w<=block.x+block.w&&b.y>=block.y&&b.y+b.h<=block.y+block.h).map(b=>b.height))}));
renderer.setCity(instances,{width:cityModel.width,height:cityModel.height});
@@ -801,10 +805,15 @@ function buildSignQuads(signs,focus,radius,eye,heli,yaw){
add(flatQuad(b.x+b.w/2,b.y+b.h/2,b.height+.08,width,width/entry.aspect),entry);roofs++;
if(!heli&&d<SIGN_LIMITS.wallRadius&&walls<SIGN_LIMITS.walls){
for(const quad of wallSigns(b,eye,entry.aspect))add(quad,entry);
for(const quad of bladeSigns(b,eye,entry.aspect,bladeClearance))add(quad,entry);
walls++;
}
}
// Folder names on blades at the street corners of nearby blocks, one per side of each intersection.
if(!heli)for(const {block,buildings,label:text} of cityFolders){
if(Math.max(Math.abs(block.x+block.w/2-focus[0])-block.w/2,Math.abs(block.y+block.h/2-focus[1])-block.h/2)>SIGN_LIMITS.wallRadius)continue;
const entry=label(text,'street');if(!entry)break;
for(const quad of folderBlades(block,buildings,eye,entry.aspect,bladeClearance))add(quad,entry);
}
// Street names on the northern kerb of nearby blocks.
for(const block of cityModel.blocks){
if(block.depth<2||block.depth>3||Math.hypot(block.x+block.w/2-focus[0],block.y+block.h/2-focus[1])>radius)continue;