@tileflow/core 0.1.0-alpha.4 → 0.1.0-alpha.40

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +318 -26
  3. package/THIRD_PARTY_NOTICES.md +444 -0
  4. package/dist/3d-CzLTD0RK.d.ts +1092 -0
  5. package/dist/3d.d.ts +1 -0
  6. package/dist/3d.js +12514 -0
  7. package/dist/browser.d.ts +2281 -0
  8. package/dist/browser.js +21680 -0
  9. package/dist/build.d.ts +595 -0
  10. package/dist/build.js +29729 -0
  11. package/dist/capture-shot-ChPQvPyi.d.ts +291 -0
  12. package/dist/capture.d.ts +9 -0
  13. package/dist/capture.js +510 -0
  14. package/dist/config-reference.json +40600 -0
  15. package/dist/icon-composition-CcSHApUm.d.ts +2478 -0
  16. package/dist/index.d.ts +1806 -162
  17. package/dist/index.js +26319 -3476
  18. package/dist/manifest-types-DnhSJjCM.d.ts +902 -0
  19. package/dist/manifest.d.ts +9 -0
  20. package/dist/manifest.js +449 -0
  21. package/dist/native-profile.d.ts +206 -0
  22. package/dist/native-profile.js +9339 -0
  23. package/dist/native.d.ts +365 -0
  24. package/dist/native.js +5970 -0
  25. package/dist/runtime.d.ts +1 -0
  26. package/dist/runtime.js +1445 -0
  27. package/dist/worker/camera-tile-worker.global.js +20 -0
  28. package/docs/application-tile-reading.md +86 -0
  29. package/docs/authoring-model.md +269 -0
  30. package/docs/boundary-areas-and-choropleths.md +217 -0
  31. package/docs/buildings-3d.md +594 -0
  32. package/docs/camera-moves-and-tile-cache.md +112 -0
  33. package/docs/capture-scenes.md +40 -0
  34. package/docs/capture-shots.md +173 -0
  35. package/docs/cartographic-defaults.md +210 -0
  36. package/docs/compiled-style-performance.md +286 -0
  37. package/docs/data-is-separate-from-design.md +610 -0
  38. package/docs/depth-markers.md +65 -0
  39. package/docs/dynamic-style-images.md +100 -0
  40. package/docs/globe-atmosphere.md +113 -0
  41. package/docs/hosted-session-authorization.md +106 -0
  42. package/docs/map-and-terrain-examples.md +156 -0
  43. package/docs/map-emphasis.md +89 -0
  44. package/docs/native-artifact-profile.md +152 -0
  45. package/docs/native-resource-urls.md +369 -0
  46. package/docs/nautical-marks.md +308 -0
  47. package/docs/public-api-and-browser-subpath.md +101 -0
  48. package/docs/route-drawing.md +191 -0
  49. package/docs/shared-visual-primitives.md +101 -0
  50. package/docs/street-lights.md +127 -0
  51. package/docs/street-sky.md +156 -0
  52. package/docs/style-states.md +73 -0
  53. package/docs/sun-and-light.md +167 -0
  54. package/docs/theme-transitions-and-blends.md +317 -0
  55. package/docs/themes-and-module-styles.md +156 -0
  56. package/docs/trees-3d.md +295 -0
  57. package/package.json +67 -8
@@ -0,0 +1,594 @@
1
+ # 3D buildings
2
+
3
+ `createTileflowBuildings3D` raises the buildings of a Tileflow map's vector tiles round the camera.
4
+ By default it preserves the map's building colours, uses the scene sun, casts shadows, adds soft
5
+ contact shading and rounds exterior corners by 0.8 metres. An unspecified sun uses soft daylight
6
+ with directional intensity 0.2 and ambient intensity 0.8; authored theme lighting stays in control.
7
+ Showing, hiding and changing height are immediate. Illuminated outlines, random colour variation,
8
+ invented windows and night lights remain off. Every effect is independently overridable and
9
+ independent of the map's trees.
10
+
11
+ Streets supplies architectural flood lighting and subtle model wall emission for its dark
12
+ presets. These controls are independent of invented windows and the optional `nightLight` effects.
13
+
14
+ Tileflow landmarks and a map's own building models can stand in the same 3D scene, replacing
15
+ the buildings under their footprints.
16
+
17
+ It is a custom WebGL2 layer for MapLibre GL JS, drawn above the map's buildings and below its
18
+ labels. It needs WebGL2 and the Mercator projection, which MapLibre's globe turns into from about
19
+ zoom 12. On every map the city comes in as the camera zooms in: placed models and landmarks first,
20
+ fading in and rising from the ground between zooms 14.2 and 14.6, then the buildings between 15 and
21
+ 15.3. Their shadows and contact shading follow their height. Zooming back out lowers and fades them
22
+ again. With reduced motion (`prefers-reduced-motion`) they keep their height and only fade.
23
+ `hide()` keeps them hidden at every zoom until `show()` is called.
24
+
25
+ A map sets these bands with the buildings module's `threeDimensional.entrance`, each a
26
+ `[from, to]` pair of zooms; a band left out keeps its default:
27
+
28
+ ```ts
29
+ buildings({threeDimensional: {entrance: {buildings: [15.5, 16], models: [14.5, 15]}}});
30
+ ```
31
+
32
+ The renderer reads its tiles from a zoom below its `minzoom`, so a band that starts earlier than
33
+ that comes in from there.
34
+
35
+ ## The map's 3D scene
36
+
37
+ React, Vue and Svelte `Map` components show the 3D scene of every map whose style declares one, by
38
+ default: buildings with Tileflow landmarks and the map's Team building models, 3D trees, and street
39
+ lights where the map enables them. They load the 3D renderers from `@tileflow/core/3d` only when
40
+ the style declares a scene, so a 2D map does not download them. Set `viewMode="2d"` (Vue:
41
+ `view-mode="2d"`) to opt out: no 3D buildings or street lights are mounted, and vegetation follows
42
+ its [display policy](trees-3d.md#visibility-and-representation) for the 2D view. Changing it back to
43
+ `3d` shows the scene on the same map. React Native, image mode, Capture and Static Maps do not draw
44
+ the scene.
45
+
46
+ Applications that build their own MapLibre map mount the same scene with `createTileflowMap3D`.
47
+ It mounts each renderer the style declares, follows theme and style changes, and releases a
48
+ renderer when the style no longer declares it:
49
+
50
+ <!-- docs:check -->
51
+
52
+ ```ts
53
+ import {
54
+ createTileflowMap3D,
55
+ type TileflowMap3DMap,
56
+ type TileflowThemeController,
57
+ } from '@tileflow/core/browser';
58
+
59
+ export function showTheScene(map: TileflowMap3DMap, controller: TileflowThemeController) {
60
+ const scene = createTileflowMap3D({map, controller});
61
+ // Shown on creation; hide() and show() switch the whole scene, dispose() removes it.
62
+ return scene;
63
+ }
64
+ ```
65
+
66
+ `hide()` takes the buildings down and releases the street lights; `show()` brings them back. Both
67
+ take the same transition as the buildings' `show()` and `hide()`. `setVegetation(false)` hides
68
+ every tree and `setVegetation(undefined)` restores the map's default. To load the renderers only
69
+ for maps that need them, call `attachTileflowMap3D({map, controller, load: () =>
70
+ import('@tileflow/core/3d')})` from `@tileflow/core/browser`, as the framework components do; its
71
+ `setViewMode()` and `setVegetation()` apply once the scene mounts.
72
+
73
+ A MapLibre application that loads a compiled `style.json` directly gets no 3D scene: buildings,
74
+ landmarks, models, trees and lights are custom WebGL layers added only by this runtime. Where a map
75
+ draws a plain `fill-extrusion` building pass, the style ships it hidden and grouped under
76
+ `metadata['tileflow:3d-toggle'] = 'building'`; showing those layers gives extruded footprints only.
77
+
78
+ ## Decorative building surfaces
79
+
80
+ `threeDimensional.surfaces` adds optional brick joints, roof tiles and a painted cornice to
81
+ ordinary extruded buildings. These are artistic decoration, not surveyed materials. They keep
82
+ the building's authored colour and use its existing lighting, shadows and footprint. Custom GLB
83
+ models and landmarks retain their own materials. Leaving `surfaces` out preserves a smooth finish.
84
+
85
+ ```ts
86
+ buildings({
87
+ threeDimensional: {
88
+ surfaces: {
89
+ walls: {kind: 'brick', size: [6, 3], jointWidth: 0.16, contrast: 0.28},
90
+ roof: {kind: 'tiles', size: 10, jointWidth: 0.12, contrast: 0.12},
91
+ cornice: {height: 0.7, contrast: 0.3},
92
+ },
93
+ },
94
+ });
95
+ ```
96
+
97
+ Wall `size` is `[width, courseHeight]` in metres. Roof size is a square in projected Mercator
98
+ metres: its ground size shrinks with latitude, while its grid remains fixed through camera moves
99
+ and tile boundaries. Sizes accept 0.5–100; joints accept 0.01–2 and are limited to a quarter of the
100
+ smallest cell dimension. `contrast` accepts 0–1, including theme number tokens; zero hides that
101
+ finish. The defaults are `[6, 3]`, 0.12 and 0.2 for walls, and 8, 0.12 and 0.1 for roofs.
102
+
103
+ The cornice is a light band with a subtle seam, not an overhanging mesh. Its `height` accepts
104
+ 0.05–5 metres, defaults to 0.6 and is limited to one fifth of a wall's height; its contrast defaults
105
+ to 0.2. The roof border uses projected metres and follows exterior and courtyard edges without
106
+ drawing internal triangle edges. The shader filters joints and fades details smaller than a few
107
+ pixels. No texture downloads, extra draw calls or per-brick geometry are required. These finishes
108
+ belong to the WebGL 3D scene; portable 2D styles retain their ordinary building fill.
109
+
110
+ ## Show the buildings
111
+
112
+ This example assumes an existing MapLibre map showing a Tileflow map whose vector source is called
113
+ `tileflow`, as Tileflow's maps call World, and a theme controller for it:
114
+
115
+ <!-- docs:check -->
116
+
117
+ ```ts
118
+ import {
119
+ createTileflowBuildings3D,
120
+ type TileflowBuildings3DMap,
121
+ type TileflowThemeController,
122
+ } from '@tileflow/core/browser';
123
+
124
+ export function raiseTheCity(map: TileflowBuildings3DMap, controller: TileflowThemeController) {
125
+ const buildings = createTileflowBuildings3D({map, controller});
126
+ // Enabled on creation; hide() and show() override visibility.
127
+ return buildings;
128
+ }
129
+ ```
130
+
131
+ `show()`, `hide()` and `grow(scale)` change the height immediately by default (duration 0).
132
+ `show({transition: {duration: 800, mode: 'fade'}})` reveals full-height buildings with a
133
+ smooth opacity fade. The default animation mode is `grow`; both modes respect reduced-motion settings.
134
+ To animate, pass `{transition: {duration: 800}}` explicitly; `grow(scale)` accepts a height share
135
+ from 0 to 1. Each returns a promise that resolves once the change ends; a newer call takes over from where the buildings stand. A browser preference for reduced
136
+ motion changes at once. `dispose()` takes the layer off the map.
137
+
138
+ The renderer reads the source's z15 tiles round the camera (z15 is where World's buildings carry
139
+ their heights; a source that stops earlier is read at its own maximum zoom), up to `maxTiles`
140
+ tiles (25 by default), and meshes them a few milliseconds per frame. It reads World's
141
+ `render_height`, `render_min_height`, `hide_3d`, `height_source`, `building_tone`, `class`,
142
+ `night_use` and the roof fields (see [roofs](#roofs)), and the places in World's `poi` layer (see
143
+ [night light](#night-light)). `stats.declared` tells whether the map's
144
+ style describes its 3D buildings. Recompile existing maps to pick up the new defaults: previously
145
+ compiled metadata keeps explicit settings, including `false`, `0` and `lighting: 'style'`.
146
+
147
+ ## Design the buildings in the map
148
+
149
+ The map describes how its 3D buildings look, in its buildings module, and publishes it with its
150
+ style for every theme; the renderer follows theme changes and blends. The design is visible in
151
+ `tileflow preview` and validated with the rest of the map:
152
+
153
+ <!-- docs:check -->
154
+
155
+ ```ts
156
+ import {defineMap, expr, field, refine, refineRenderTarget} from '@tileflow/core';
157
+ import {streets} from '@tileflow/maps';
158
+
159
+ const floors = expr.divide(expr.toNumber(expr.get(field('renderHeight')), 0), 3.2);
160
+
161
+ export default defineMap({
162
+ id: 'lit-city',
163
+ name: 'Lit city',
164
+ version: 1,
165
+ extends: streets,
166
+ modules: {
167
+ buildings: refine({
168
+ threeDimensional: {
169
+ lighting: 'sun',
170
+ shadows: true,
171
+ occlusion: true,
172
+ edges: 1,
173
+ colorVariation: 0.06,
174
+ windows: true,
175
+ nightLight: 0.6,
176
+ edgeColor: '#2b2a33',
177
+ },
178
+ renderStack: {
179
+ // Core's 3D buildings take the colour of the map's 3D building pass.
180
+ tints: refineRenderTarget({
181
+ target: 'buildings.render.extrusion',
182
+ renderer: 'extrusion',
183
+ style: {
184
+ color: expr.step(expr.modulo(floors, 3), '#e8d9c4', [
185
+ [1, '#d6e2d8'],
186
+ [2, '#e5d4e6'],
187
+ ]),
188
+ },
189
+ }),
190
+ },
191
+ }),
192
+ },
193
+ });
194
+ ```
195
+
196
+ - Colour: each building takes the colour the map gives its 3D buildings: Streets' 3D building pass
197
+ (the theme's `buildings.extrusion`), else the buildings module's extrusion, else its roofs. A
198
+ colour that depends on the building's data is evaluated per building and theme when a tile or the
199
+ set of themes changes, and the themes either side of a blend's position mix on the GPU, so a
200
+ moving blend costs nothing per frame.
201
+ - `threeDimensional.shadows` and `occlusion` (the band at the foot of the walls and the darker tight
202
+ corners) default to true. Set either to false independently. `windows: true` adds invented night windows, lit by rooms; they are
203
+ decoration, not data, and are off by default. Where World says what a building is used for
204
+ (`class`), sheds, garages, barns, factories, churches and service buildings get no rows of
205
+ windows, homes light more of theirs at night, in a warm light, and offices, schools and hospitals
206
+ fewer, in a cooler one.
207
+ - `threeDimensional.edges` and `edgeColor` set the [edges](#edges), and `nightLight` the
208
+ [night light](#night-light). Numbers and colours may be theme tokens.
209
+ - `threeDimensional.edgeRadius` rounds convex exterior corners in the mesh and softens the
210
+ roof/wall lighting transition. It accepts 0–1 metres and defaults to 0.8; use 0 for sharp corners.
211
+ Courtyard corners and tile cuts stay unchanged. The authored roof shape is preserved; the top
212
+ edge is softened through lighting, not a geometric roof bevel. Rounding adds a bounded number
213
+ of vertices at convex corners, with no extra render pass. Changes to this radius rebuild the
214
+ resident building meshes.
215
+
216
+ In both `style` and `sun` lighting, the vertical gradient darkens the foot of a facade by ten percent, fading smoothly over
217
+ at most three metres (half the height for low buildings). It is evaluated per fragment, so a tall
218
+ building does not acquire a full-height dark ramp. Disabling the authored extrusion's
219
+ `verticalGradient` disables this shading. Ground contact shadow is controlled by `occlusion`, enabled by default. It extends three metres
220
+ from grounded walls, darkening the ground by at most 16%, with a smooth distance falloff and round
221
+ corners. Every map uses these defaults when it enables `occlusion`. The reach is measured in world
222
+ metres, so its screen width scales continuously with zoom and becomes subtle at distant views.
223
+ Overlaps use the strongest
224
+ shade once, so shared walls, courtyards and tile joins do not accumulate dark bands. Raised building
225
+ parts do not cast contact shade. Facade occlusion darkens the base by at most 14% and fades
226
+ exponentially with a three-metre decay radius: about 5% remains one floor up and 2% two floors up.
227
+ It follows height above ground, so raised parts do not restart the contact gradient at their base.
228
+ The facade gradient, contact and inner-corner shade use the strongest contribution once, avoiding
229
+ an extra dark strip where they overlap. Both lighting modes use the same falloff at every zoom.
230
+ The ground mask uses one byte per drawing-buffer pixel while enabled and is released when disabled
231
+ or the renderer is removed; it resizes with the drawing buffer.
232
+
233
+ ## Landmarks
234
+
235
+ Maps on Tileflow World show Tileflow's landmarks, such as the Eiffel Tower, as 3D models in place of
236
+ the buildings under them. They come from a landmarks tileset kept apart from the map's data: each of
237
+ its tiles carries the models standing in it, with their levels of detail and footprints, and each
238
+ region's index names its tiles by their hash, with where each landmark stands and its footprint.
239
+ The renderer reads the tileset's TileJSON, then only the indexes of the regions round the camera
240
+ and the landmark tiles near it, from a zoom before the buildings show, so a landmark in Paris costs
241
+ nothing in Madrid. New and improved landmarks reach every map without a new build of the map.
242
+
243
+ The buildings under a landmark are never raised and then taken away. A tile of buildings is built
244
+ once the indexes round it have arrived, waiting at most a second and a half; while a landmark's
245
+ tile is still on its way, the footprint its index names keeps the buildings under it flat, and the
246
+ landmark fades in when it arrives. A landmark tile that fails, or has not arrived three seconds
247
+ after the buildings round it show, lets those buildings rise until it does. A tileset that cannot be read is tried again
248
+ after five minutes, and the buildings stay drawn meanwhile.
249
+
250
+ A map switches them off, or names the TileJSON of another landmarks tileset:
251
+
252
+ <!-- docs:check -->
253
+
254
+ ```ts
255
+ import {defineMap, refine} from '@tileflow/core';
256
+ import {streets} from '@tileflow/maps';
257
+
258
+ export default defineMap({
259
+ id: 'no-landmarks',
260
+ name: 'Streets without landmarks',
261
+ version: 1,
262
+ extends: streets,
263
+ modules: {
264
+ buildings: refine({threeDimensional: {landmarks: false}}),
265
+ },
266
+ });
267
+ ```
268
+
269
+ The map's own building models come first: a landmark with the same id as one of them, or whose
270
+ footprint meets one's, is not drawn.
271
+
272
+ ## Building models
273
+
274
+ A map can place its own 3D models of buildings in the city: a landmark the extruded footprints draw
275
+ badly, or the buildings an application presents. Each model stands where the map says, in the same
276
+ light, shadows and theme as the buildings round it, and the buildings under its footprint are no
277
+ longer raised while it stands. Put glTF binaries (`.glb`) in a directory, declare it in the map's
278
+ `models`, and say where each model stands:
279
+
280
+ <!-- docs:check -->
281
+
282
+ ```ts
283
+ import {defineMap, fixed, refine} from '@tileflow/core';
284
+ import {streets, streetsModels} from '@tileflow/maps';
285
+
286
+ export default defineMap({
287
+ id: 'paris-landmarks',
288
+ name: 'Paris landmarks',
289
+ version: 1,
290
+ extends: streets,
291
+ // ./models holds eiffel-tower.glb, and eiffel-tower.mid.glb and eiffel-tower.far.glb for
292
+ // farther views.
293
+ // Keep Streets' tree models alongside the building models.
294
+ models: [streetsModels, './models'],
295
+ modules: {
296
+ buildings: refine({
297
+ threeDimensional: {
298
+ models: [{model: 'eiffel-tower', at: [2.294481, 48.858258], heading: 0}],
299
+ modelColors: {
300
+ surface: fixed('#7a5c45', {reason: "The tower's bronze paint"}),
301
+ accent: fixed('#5b4332', {reason: "The tower's bronze paint, in shadow"}),
302
+ },
303
+ },
304
+ }),
305
+ },
306
+ });
307
+ ```
308
+
309
+ `at` is the longitude and latitude of the model's origin and `heading` the degrees clockwise from
310
+ north its own north is turned to. A map places up to 256 models; one model may stand in several
311
+ places. A placement may instead name a prepared model file served elsewhere with `url`, its HTTPS
312
+ address; the map then needs no `.glb` of its own for it.
313
+
314
+ How to prepare a model:
315
+
316
+ - **Materials name the parts.** Name each material `surface`, `secondary`, `glass`, `accent` or
317
+ `roof` (an exporter's `.001` suffix is ignored); a model whose materials name a tree's parts is a
318
+ [tree model](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md)
319
+ instead. Each part takes its colour from `modelColors`, or from the buildings' colour when the
320
+ design gives it none, so the model follows the theme from day to night. Like every visual
321
+ colour, a `modelColors` entry is a theme token such as `token.color('buildings.modelRoof')`, or
322
+ a literal wrapped in `fixed(value, {reason})`; vertex colours lighter or
323
+ darker than the rest of their part shade it within that colour.
324
+ - **Architectural roles control lighting.** A material may set
325
+ `extras.tileflow.lightingRole` to `wall`, `door`, `roof`, `window`, `lamp`, `logo` or
326
+ `unclassified`. This preserves its palette slot. Without a role, `surface`, `secondary` and
327
+ `accent` are walls, `glass` is a window, and `roof` is a roof.
328
+ - **Metres round an origin on the ground.** The origin is where the model stands, with the model
329
+ above it and its own north along +y once read Z up; nothing may lie more than half a metre below
330
+ it.
331
+ - **What it stands in for.** A mesh whose material is named `footprint` is never drawn: it lays
332
+ out on the ground what the model stands in for, such as a landmark's whole base. Without one, the
333
+ model's own outline on the ground stands in, courtyards included.
334
+ - **Levels of detail.** `<id>.mid.glb` and `<id>.far.glb`, when present, are drawn farther from the
335
+ camera's centre, from about 900 m and 2.6 km.
336
+ - **Budgets.** A level has at most 200,000 triangles and 200,000 vertices, and a map at most 64
337
+ building models. Triangles of the default scene are read with their node transforms; textures,
338
+ animations and compressed geometry (Draco or meshopt) are not.
339
+
340
+ The build reads each model, reports a problem with the file and what to change, works out its
341
+ footprint and writes it beside the style as a prepared model file: a glTF binary whose meshes are
342
+ quantised (`KHR_mesh_quantization`) and compressed by meshoptimizer (`EXT_meshopt_compression`),
343
+ which any glTF viewer opens. The renderer downloads a model only when it stands near the tiles round
344
+ the camera, whether or not those tiles hold buildings, keeps the nearest dozen, and builds the tiles
345
+ under it again without the buildings that
346
+ reach more than half a metre into its footprint; a neighbour that only touches it stays. A tile of
347
+ buildings near a model waits for it, at most a second and a half, so its buildings are not raised
348
+ and then taken away, and the model fades in when it comes to stand. A model that cannot be
349
+ downloaded leaves those buildings standing. Reading a prepared model file loads
350
+ meshoptimizer's WebAssembly decoder, so a page with a Content Security Policy allows
351
+ `'wasm-unsafe-eval'` in its `script-src`.
352
+
353
+ Preparation also computes local geometric ambient visibility, independently of the model's
354
+ colour. `modelOcclusion` sets its strength from zero to one (default `0.75`); `occlusion: false`
355
+ disables it. Occluded cavities receive less ambient light while direct sun and dynamic shadows
356
+ remain independent. Existing prepared files with fully exposed vertices retain their appearance.
357
+ Reprepare their original GLBs to gain geometric occlusion. See
358
+ [model preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/building-models.md)
359
+ for authoring, sampling and costs.
360
+
361
+ ## Team 3D models
362
+
363
+ On Tileflow Hosted, a map's building models are its Team's 3D models: each is published once, under
364
+ its name, and every map of the Team that places a model of that name draws it. Locally, the map
365
+ draws its own `.glb` files, so the map is previewed and developed without an account.
366
+
367
+ ```sh
368
+ npx tileflow models publish ./models/eiffel-tower.glb --team @acme
369
+ npx tileflow models status eiffel-tower --team @acme
370
+ ```
371
+
372
+ `tileflow models publish` prepares each building model as a build does and publishes it as the
373
+ model's new current version; with no path, it publishes the building models of every map in the
374
+ project's config. Publishing the bytes of the current version again changes nothing. A hosted map
375
+ follows each model's current version: a new version, or `tileflow models restore`, which makes the
376
+ previous version current again, changes every map that places it the next time the map loads,
377
+ without deploying the maps again. A model keeps its current and previous versions.
378
+
379
+ `tileflow deploy` writes each placement of a building model as the placeholder
380
+ `tileflow://team-models/<name>` (`tileflowTeamModelUrl(name)`), which the deploy replaces with the
381
+ model's address in the map's Team; a model the Team has not published fails the deploy. The deploy
382
+ also reports a model whose published version was prepared from other files than the map's, so an
383
+ outdated model is easy to spot. A model's file, and what Tileflow shows about it, can be read
384
+ without decoding its meshes with `readTileflowModelFileSummary`. Models are served publicly and may
385
+ be cached, so they must not hold secrets or restricted personal data.
386
+
387
+ ## Roofs
388
+
389
+ A building whose tiles carry a roof shape (World's `roof_shape`) gets that roof: `gabled`,
390
+ `hipped`, `half_hipped`, `pyramidal`, `skillion`, `gambrel`, `mansard` and `saltbox` roofs, and
391
+ faceted `dome`, `onion` and `round` ones. The ridge follows the building's long side, or
392
+ `roof_orientation` and `roof_direction` when the tiles state them, and the walls rise to the roof,
393
+ so gable ends are walls. Each slope takes the sun, shadows and edges like any face: the ridge and
394
+ the hips are drawn as edges too.
395
+
396
+ The roof's height is `roof_height` where the tiles state it, taken from the building's height.
397
+ Otherwise the roof keeps the pitch of its shape (30° for a gabled or hipped roof, a half sphere for a
398
+ dome); it stands on the walls when the height came from levels or a fallback, and reaches the
399
+ building's height when that was measured (`height_source` `height`). A building without a roof
400
+ shape, a flat roof, a building with courtyards or one far from a rectangle (an L, a U) keeps a flat
401
+ roof, and so does a building a tile's buffer cuts short.
402
+
403
+ ## Light and shadows
404
+
405
+ Sun lighting and cast shadows are SDK defaults, including for Streets. Each map can choose its
406
+ own finish. For native extrusion lighting, sharp corners and no shadows in an extending map:
407
+
408
+ <!-- docs:check -->
409
+
410
+ ```ts
411
+ import {defineMap, refine} from '@tileflow/core';
412
+ import {streets} from '@tileflow/maps';
413
+
414
+ export default defineMap({
415
+ id: 'graphic-streets',
416
+ name: 'Graphic streets',
417
+ version: 1,
418
+ extends: streets,
419
+ modules: {
420
+ buildings: refine({
421
+ threeDimensional: {lighting: 'style', edgeRadius: 0, shadows: false, occlusion: false},
422
+ }),
423
+ },
424
+ });
425
+ ```
426
+
427
+ Select `timeOfDay` through your map adapter to change the authored direction, colour and strength
428
+ of direct and ambient light. Streets includes moonlight and shadows in its night presets. These
429
+ presets are independent of the user's clock and location. In Inspector, use the 3D view and
430
+ **Map → Buildings → Building light / Cast shadows**. The theme's `lighting.sun.shadowIntensity`
431
+ controls shadow strength; `lighting.ambient` fills the shade. Use `controller.setSun({date,
432
+ location})` for an astronomical direction instead, as described in
433
+ [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md).
434
+
435
+ `threeDimensional.lighting: 'style'` uses MapLibre's `light` position,
436
+ anchor, colour and intensity, together with the authored extrusion's `verticalGradient`.
437
+ Changing the scene sun does not change this base lighting. The style's native lighting defaults
438
+ apply when it declares no light.
439
+
440
+ With the default `lighting: 'sun'`, the light comes from `controller.getSun()`: the theme's sun, mixed during a blend, or the sun an
441
+ application places by direction or by date and place (see
442
+ [sun and light](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/sun-and-light.md)).
443
+ Without a controller, the renderer reads the compiled style's sun, falling back to the style's
444
+ light direction and then the SDK daylight. It refreshes this cached light on style changes.
445
+ A sunlit roof keeps the building's colour, a wall turned to a low sun glows, and shade takes the
446
+ sky's colour. Shadows lengthen as the sun lowers, with their projected direction limited to 15°
447
+ above the horizon. The actual light retains its authored altitude; a sun below the horizon casts none.
448
+
449
+ A wall sees half the sky, so at night, when the ground gives back little light, walls in shade
450
+ stay darker than roofs. A theme's night `lighting.sun` set high (60° or more) lights roofs more than
451
+ any wall, so roofs and walls read apart under the moon.
452
+
453
+ MapLibre's own layers take no shadows, so the renderer darkens the map it is drawn over: after its
454
+ buildings, it multiplies the ground by the shadows and by a band at the foot of the walls. Labels
455
+ and icons drawn above it keep their colours. Layers kept under 3D content with
456
+ `createTileflowGroundLayers` are darkened with the ground. Other Core 3D layers on the map, such as
457
+ [3D trees](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/trees-3d.md), share
458
+ the buildings' shadows when `shadows: true`. With it off, buildings neither cast nor receive
459
+ scene shadows, even when another 3D layer uses them.
460
+
461
+ ## Edges
462
+
463
+ Where one building ends and the next starts stays legible at any distance: each roof's outline and
464
+ each building's outer corners catch the light in a line one CSS pixel wide, whatever the zoom, when enabled. `colorVariation` independently varies building brightness by up to the specified
465
+ fraction (0–1); it defaults to 0, so authored colours remain unchanged. Use 0.06 for the earlier
466
+ 6% variation. The lines are strongest at night; diagonals inside a roof, inner corners and the cuts at
467
+ a tile's edge draw none. `edges` sets their strength from 0 (the default, none) to 1.
468
+
469
+ `edgeColor` gives the lines a colour of the map's own, per theme through a token: a dark outline as
470
+ on a printed map, or a neon one at night. It is drawn as it is, unlit, at the full `edges` strength
471
+ by day and night, mixed between the themes either side of a blend's position.
472
+
473
+ ## Night light
474
+
475
+ Architectural illumination has two independent controls:
476
+
477
+ - `floodLight` selects ordinary buildings by height. Ground light starts at `minGroundHeight`
478
+ (30 m by default), with a stable per-building `groundRadius` in metres (default `[30, 100]`).
479
+ Wall light starts above `minWallHeight` (200 m); its reach is building height times
480
+ `wallHeightRatio` (default `1/3`). `intensity` is zero to one and `color` is a theme colour.
481
+ The effect fades with scene brightness; `brightnessScale` compensates an authored light-colour
482
+ transform and defaults to one. Overlapping ground contributions use their strongest value.
483
+ - `modelLighting` assigns physical materials and emission to architectural roles. Each profile accepts
484
+ `emissiveStrength` (zero to five), optional `emissiveColor` (otherwise the surface's own colour), and
485
+ `emissiveHeight`: `[beginHeight, endHeight, beginMultiplier, endMultiplier, curve]`.
486
+ Heights are fractions of the source mesh's vertical bounds (the full model for legacy files),
487
+ multipliers are zero to one, and the exponent is
488
+ `10 ** curve`, with `curve` from -1 to 1. Equal heights give a constant multiplier of one.
489
+ Every gradient component, strength and colour may use theme tokens. Emission is mixed in linear light and is independent
490
+ of ambient occlusion and solar shadows. Values above one saturate the surface emission at one;
491
+ they also control the falloff of explicitly authored area emitters.
492
+
493
+ Physical profiles also accept `color`, `colorMix` (zero to one, default one when a tint exists),
494
+ `roughness` and `metallic` (zero to one). Explicit `modelColors` override role tints. Missing
495
+ physical overrides retain the glTF material. `emissiveVariation` adds a stable, model-seeded amount
496
+ from zero to its value. Optional `colorVariation` supplies independent `[min, max]` ranges for
497
+ `hue` (0–360), `saturation` and `lightness` (0–100), with `space: 'srgb'` (default) or `'linear'`.
498
+ The same model identity keeps its samples across LODs and theme changes.
499
+
500
+ Streets leaves ordinary flood lighting off in its light presets. Its dusk/night wall profile uses a strength
501
+ of `0.1` and `[0.5, 0, 0, 1, 0.8]`, concentrating a warm wash near the foot. Monochrome uses a
502
+ weaker neutral wall profile and no architectural ground flood. Prepared models without role
503
+ metadata use their documented palette mapping. Windows use metallic reflection and varied emission;
504
+ doors and logos have separate profiles, and roofs remain non-emissive. Day/dawn profiles follow
505
+ the scene's brighter material response. A role does not generate missing windows or ground emitters.
506
+ See [building-model preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/building-models.md)
507
+ for glTF channels, explicit area emitters, compatibility and resource costs.
508
+
509
+ The separate `nightLight` control retains its decorative building and shop effects:
510
+
511
+ With `nightLight` above 0, the buildings light the street from data at night (as the sun's
512
+ `brightness` falls):
513
+
514
+ - Landmarks (World's `building_tone` `destination`) and towers of 40 m or more, one in two, are
515
+ flood-lit at their foot, on the wall and the ground around it.
516
+ - Buildings that hold places busy at night light the windows of their ground floor and spill their
517
+ light a few metres onto the pavement: bars and clubs in a neon of their own or a warm light,
518
+ theatres and cinemas in a bright white, places to eat in amber. A building takes the strongest
519
+ use of its places, nightlife before entertainment before dining. Walls round a courtyard stay
520
+ dark.
521
+
522
+ The night use comes from World's `night_use` on each building. In tiles whose buildings do not
523
+ carry it, the renderer finds it in the tile's places: a place inside a building, or within a few
524
+ metres of its facade, gives the building its use.
525
+
526
+ `nightLight` sets how bright all of this shines, from 0 (none) to 2; it defaults to 0.
527
+
528
+ For the streets themselves, see
529
+ [street lights](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/street-lights.md),
530
+ which draws over the buildings so they hide the lamps behind them; both layers share each tile's
531
+ download.
532
+
533
+ ## Options
534
+
535
+ The renderer's own options say only how it is delivered: `minzoom` (15 by default; the default
536
+ entrance bands start from it), `maxTiles`,
537
+ `beforeId` to place the layer before another one, and `transformRequest` to apply the map's request
538
+ rules to the tiles it reads.
539
+
540
+ ## Cost
541
+
542
+ During partial opacity, a depth-only geometry pass resolves the nearest surfaces before colour
543
+ blending, preventing rear walls from bleeding through. At full opacity only the normal geometry
544
+ pass runs; zoom fading schedules no extra animation frames or per-zoom mesh rebuilds.
545
+
546
+ The browser bundle builds from the tiles' bytes in one worker that the page's 3D buildings, trees
547
+ and street lights share, nearest tiles first: it decodes each tile once for all of them, meshes its
548
+ buildings, keeps their indices to 16 bits when the vertices allow, lays out the walls' contact-band
549
+ segments and transfers the finished arrays. The renderer uploads them to the GPU and keeps no copy;
550
+ a style reloaded in full keeps the uploaded buffers. No worker URL or extra asset hosting is needed.
551
+ A Content Security Policy must allow `worker-src blob:` to use this path; if workers are
552
+ unavailable or blocked, the layers build in the page instead. The worker is terminated when the
553
+ last of those layers is disposed. Tiles the camera has left are not downloaded or built. Pending tile updates retain
554
+ the previous mesh until their replacement is ready; cancelled and obsolete results are discarded.
555
+
556
+ Mercator rendering batches buildings spatially and skips batches outside the camera. Shadow
557
+ casting uses the light's bounds independently, so an offscreen building can still cast a visible
558
+ shadow. Ground contact and flood bands retain their physical reach when testing visibility.
559
+ Rounded corners use fewer segments when their screen radius is small and retain the original
560
+ tessellation up close. Latitude, radius and pitch inform this choice; very oblique views keep full
561
+ detail. Changes are prepared asynchronously; heights, feature colours,
562
+ and the contact-shadow settings stay the same. GPU uploads remain limited to two tiles per frame.
563
+
564
+ The shared shadow map is at most 4096 × 4096. On a planar view it concentrates resolution on visible
565
+ ground and raised surfaces, while every loaded caster can still project into that area. A snapped
566
+ Mercator texel grid stays stable during small pans, including changes of latitude, without fixing
567
+ the meshes' physical metre scale. One filtered depth comparison keeps edges sharp.
568
+ Missing camera capabilities, terrain or a view near the horizon use the full loaded area instead.
569
+ The depth pass runs again when its focused area, light or casting geometry changes and is reused
570
+ when they stay unchanged. While the camera moves, the focused area takes a quarter of its span as a
571
+ margin on each side and is kept while it still covers the view at a similar resolution, so the
572
+ depth pass is not drawn on every frame; once the camera stops, it is fitted to the view again.
573
+
574
+ Model ambient visibility and lighting roles retain the existing 16-byte vertex stride. They add
575
+ no geometry pass or texture. Architectural ground lighting uses a separate one-byte-per-pixel
576
+ mask and one geometry/composite pair of passes while active; its mask is released when inactive.
577
+ The contact band and the architectural light draw their masks in the layer's prerender, before
578
+ the map's main pass, so the map's framebuffer is never switched while its layers draw.
579
+ At 1280 × 720 and DPR 1, the mask occupies 921,600 bytes, excluding driver overhead.
580
+
581
+ ## Limits
582
+
583
+ - Mercator only; the layer draws nothing below its `minzoom`.
584
+ - No terrain: buildings stand on flat ground.
585
+ - One building colour per feature and theme, on its roof and walls alike; facades are not drawn.
586
+ - Roofs follow the building's smallest enclosing rectangle: a building with courtyards or far from a
587
+ rectangle keeps a flat roof, and `sawtooth` roofs are drawn flat.
588
+ - A colour expression is evaluated at zoom 16.
589
+ - Building models and landmarks take no illuminated edges and cannot be picked or styled by state.
590
+ - Model materials use semantic colours and role emission; physical roughness, metallic
591
+ reflections and authored textures are not rendered.
592
+ - A hosted map places its building models as its Team's 3D models: `tileflow deploy` uploads no
593
+ model files (see [Team 3D models](#team-3d-models)). A deploy with `--with-native` does not take
594
+ them, since MapLibre Native draws no models. Models named by `url` deploy as they are.