Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion docs/MAP_3D_CITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,7 +223,15 @@ tile the camera sees**, each at the level of detail its distance affords
(`cityMeshes.ts` → `groundTileFor`); a tile is not drawn until it is lifted.
- **Fade.** The meshes grow in over exactly the zooms `building-3d` does
(`BUILDINGS_3D_FADE`, 15.5 → 16.5), so switching renderers changes how
buildings look, never *when* they appear.
buildings look, never *when* they appear. The fade is the same screen door
as a level's (`tsl/common.ts` → `dithered`): at z16 half the screen's
pixels draw the city and half the map. Each pixel is the front surface of
the city or the map, never a blend. The buildings stay opaque and keep
their depth, so no wall or street shows through another surface. Blended
at the fade's alpha (as until main @52dde03), 37% of the city's pixels at
z16 mixed in a surface behind the front one: walls and streets through the
blocks (Madrid, owner report). Checked by the visibility gate's mid-fade
view (`check-city3d-visible`, nosky leg).

#### Measured per tile (worker, Bun, one core)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,6 @@ const light = {
uLightPosition: v(),
uLightIntensity: n(0.5),
uLightColor: v(),
uOpacity: n(1),
uGrounded: n(0),
};
const context = () => ({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ describe('the shader', () => {
uLightPosition: { value: new Vector3(0, 0, 1) },
uLightIntensity: { value: 0.5 },
uLightColor: { value: new Vector3(1, 1, 1) },
uOpacity: { value: 1 },
uGrounded: { value: 0 },
}),
sky,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,6 @@ function spyMaterials() {
uLightPosition: { value: new Vector3(0, 0, 1) },
uLightIntensity: { value: 0.5 },
uLightColor: { value: new Vector3(1, 1, 1) },
uOpacity: { value: 1 },
uGrounded: { value: 0 },
};
const sky = createSkyNodes(skyRefs(true), 12);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,6 @@ describe('the shaders', () => {
uLightPosition: { value: new Vector3(0, 0, 1) },
uLightIntensity: { value: 0.5 },
uLightColor: { value: new Vector3(1, 1, 1) },
uOpacity: { value: 1 },
uGrounded: { value: 0 },
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,6 @@ describe('tree shapes and shader', () => {
uLightPosition: v(),
uLightIntensity: { value: 0.5 },
uLightColor: v(),
uOpacity: { value: 1 },
uGrounded: { value: 0 },
},
THREE.BackSide,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -145,7 +145,6 @@ describe('the TSL building material', () => {
uLightPosition: { value: new Vector3(0.5, 0.5, 1) },
uLightIntensity: { value: 0.2 },
uLightColor: { value: new Vector3(1, 1, 1) },
uOpacity: { value: 1 },
uGrounded: { value: 0 },
});

Expand Down
7 changes: 4 additions & 3 deletions packages/frontend/components/map/engine/city3d/cityEngine.ts
Original file line number Diff line number Diff line change
Expand Up @@ -508,7 +508,6 @@ export function createCityEngine(options: CityEngineOptions): CityEngine {
uLightPosition: { value: new THREE.Vector3(0, 0, 1) },
uLightIntensity: { value: 0.5 },
uLightColor: { value: new THREE.Vector3(1, 1, 1) },
uOpacity: { value: 1 },
// On 3D terrain: read the per-vertex lift (`terrainGround.ts`).
uGrounded: { value: 0 },
// The night seam (`setNightFactor`): read by every night-time look.
Expand Down Expand Up @@ -1173,7 +1172,6 @@ export function createCityEngine(options: CityEngineOptions): CityEngine {

const span = options.fade.end - options.fade.start;
const opacity = Math.max(0, Math.min(1, (view.zoom - options.fade.start) / span));
light.uOpacity.value = opacity;
treeUniforms.uTreeMinHeight.value = treeMinHeightM(view.zoom);

const center = { x: mercatorXFromLng(view.center.lng), y: mercatorYFromLat(view.center.lat) };
Expand Down Expand Up @@ -1269,7 +1267,10 @@ export function createCityEngine(options: CityEngineOptions): CityEngine {
}
placeEntry(entry, entry.placement, center);
setEntryTreeDetail(entry, shapes, r(entry) < nearTreesWithin);
setEntryFade(entry, from, to);
// The zoom fade (`BUILDINGS_3D_FADE`) is the same screen door as a
// level's: its share of the transition's pixels, so the city draws as
// one solid layer, and never shows what is behind a wall through it.
setEntryFade(entry, from * opacity, to * opacity);
setEntryVisible(entry, true);
drawnEntries.add(entry);
for (const source of entrySources.get(entry) ?? []) sources.add(source);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import {
dithered,
extrusionLit,
mapBlending,
premultiplied,
opaque,
terrainLift,
type UniformRef,
} from './common';
Expand All @@ -66,7 +66,6 @@ export interface BuildingUniformRefs {
uLightPosition: UniformRef<Vector3>;
uLightIntensity: UniformRef<number>;
uLightColor: UniformRef<Vector3>;
uOpacity: UniformRef<number>;
uGrounded: UniformRef<number>;
}

Expand All @@ -84,7 +83,6 @@ export function createBuildingUniforms(refs: BuildingUniformRefs) {
uLightPosition: bindVec3(refs.uLightPosition),
uLightIntensity: bindFloat(refs.uLightIntensity),
uLightColor: bindVec3(refs.uLightColor),
uOpacity: bindFloat(refs.uOpacity),
uGrounded: bindFloat(refs.uGrounded),
};
}
Expand Down Expand Up @@ -166,7 +164,7 @@ function buildMaterial(
const ground = float(AO.groundMin).add(
float(1 - AO.groundMin).mul(smoothstep(0, AO.groundReachM, vHeightAboveGround)),
);
const color = premultiplied(extrusionLit(base, n, ground.mul(vCornerAo), u), u.uOpacity);
const color = opaque(extrusionLit(base, n, ground.mul(vCornerAo), u));

const material = mapBlending(new MeshBasicNodeMaterial());
// The lift is metres; stored z is 1/32 m, scaled back by the model matrix.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,6 @@ export async function createCityTileView(options: CityTileViewOptions): Promise<
uLightPosition: v(light.position),
uLightIntensity: { value: light.intensity },
uLightColor: v(light.color),
uOpacity: { value: 1 },
uGrounded: { value: 0 },
});
// `cityMeshes.ts` → SIDE: the stored units mirror y once. In the map that is
Expand Down
13 changes: 8 additions & 5 deletions packages/frontend/components/map/engine/city3d/tsl/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,12 +76,11 @@ export function perObject(key: string, read: (value: unknown) => number, fallbac
});
}

/** The light the engine hands every material (`occlusion.ts` → `CityLight`), plus opacity and terrain. */
/** The light the engine hands every material (`occlusion.ts` → `CityLight`), plus terrain. */
export interface LightUniforms {
uLightPosition: ReturnType<typeof bindVec3>;
uLightIntensity: ReturnType<typeof bindFloat>;
uLightColor: ReturnType<typeof bindVec3>;
uOpacity: ReturnType<typeof bindFloat>;
uGrounded: ReturnType<typeof bindFloat>;
}

Expand All @@ -107,9 +106,13 @@ export function extrusionLit(base: Vec3, n: Vec3, occlusion: Float, u: LightUnif
);
}

/** Premultiplied, as MapLibre blends custom layers. */
export const premultiplied = (color: Vec3, opacity: Float): Node<'vec4'> =>
vec4(color.mul(opacity), opacity);
/**
* An opaque surface's output. The buildings and trees are never blended: the
* zoom fade (`BUILDINGS_3D_FADE`) is the dither's, as every other fade
* ({@link dithered}), so they draw as one solid layer with their depth, and
* nothing behind a wall shows through it.
*/
export const opaque = (color: Vec3): Node<'vec4'> => vec4(color, 1);

/** Blend as MapLibre blends custom layers: premultiplied alpha, opaque pass. */
export function mapBlending<T extends NodeMaterial | MeshBasicNodeMaterial>(material: T): T {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { Z_UNITS_PER_METRE } from '../packing';
import type { CityTileFormat } from '../protocol';
import { cityTileSurface, rampAt, temperedTag } from './buildingSurface';
import type { BuildingUniforms } from './buildingMaterial';
import { dithered, type Float, mapBlending, premultiplied, terrainLift, type Vec3 } from './common';
import { dithered, type Float, mapBlending, opaque, terrainLift, type Vec3 } from './common';
import { rolloff } from './detailedMaterial';
import type { SkyNodes } from './skyNodes';

Expand Down Expand Up @@ -267,7 +267,7 @@ function buildMaterial(
};
const lit = max(response(surface, light), vec3(0)).pow(1 / 2.2);
// Aerial perspective, the same as the ground pass's.
const color = premultiplied(sky.aerial(lit, vWorld), u.uOpacity);
const color = opaque(sky.aerial(lit, vWorld));

const material = mapBlending(new MeshBasicNodeMaterial());
material.positionNode = local;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ import {
extrusionLit,
mapBlending,
perObject,
premultiplied,
opaque,
terrainLift,
type UniformRef,
} from './common';
Expand Down Expand Up @@ -76,7 +76,6 @@ export interface TreeUniformRefs {
uLightPosition: UniformRef<Vector3>;
uLightIntensity: UniformRef<number>;
uLightColor: UniformRef<Vector3>;
uOpacity: UniformRef<number>;
uGrounded: UniformRef<number>;
}

Expand Down Expand Up @@ -104,7 +103,6 @@ export function createTreeMaterials(refs: TreeUniformRefs, side: Side): TreeMate
uLightPosition: bindVec3(refs.uLightPosition),
uLightIntensity: bindFloat(refs.uLightIntensity),
uLightColor: bindVec3(refs.uLightColor),
uOpacity: bindFloat(refs.uOpacity),
uGrounded: bindFloat(refs.uGrounded),
};
const built = new Map<boolean, MeshBasicNodeMaterial>();
Expand Down Expand Up @@ -136,7 +134,6 @@ function buildMaterial(
uLightPosition: ReturnType<typeof bindVec3>;
uLightIntensity: ReturnType<typeof bindFloat>;
uLightColor: ReturnType<typeof bindVec3>;
uOpacity: ReturnType<typeof bindFloat>;
uGrounded: ReturnType<typeof bindFloat>;
},
side: Side,
Expand Down Expand Up @@ -264,11 +261,9 @@ function buildMaterial(
mix(float(AO.groundMin + 0.1), 1, smoothstep(-0.6, 0.55, n.z)).mul(mix(0.9, 1.05, vCrown)),
float(AO.groundMin).add(smoothstep(0, 3, vHeight).mul(0.2)),
);
const lit = premultiplied(extrusionLit(surface, n, occlusion, u), u.uOpacity);
const lit = opaque(extrusionLit(surface, n, occlusion, u));
// The contact shadow: premultiplied black, fading to nothing at its rim.
const shadowAlpha = float(TREE_SHADOW_ALPHA)
.mul(float(1).sub(smoothstep(0.15, 1, vRadial)))
.mul(u.uOpacity);
const shadowAlpha = float(TREE_SHADOW_ALPHA).mul(float(1).sub(smoothstep(0.15, 1, vRadial)));
const color = select(vPart.greaterThan(SHADOW - 0.5), vec4(vec3(0), shadowAlpha), lit);

const material = mapBlending(new MeshBasicNodeMaterial());
Expand Down
Loading
Loading