@tileflow/core 0.1.0-alpha.35 → 0.1.0-alpha.36
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.
- package/README.md +16 -17
- package/dist/browser.d.ts +600 -427
- package/dist/browser.js +1633 -431
- package/dist/build.d.ts +3 -3
- package/dist/build.js +1734 -1093
- package/dist/config-reference.json +2901 -1906
- package/dist/{icon-composition-DSjIKmrK.d.ts → icon-composition-sN3JkXdb.d.ts} +291 -93
- package/dist/index.d.ts +201 -7
- package/dist/index.js +4691 -3509
- package/dist/{manifest-types-Bzxon3dx.d.ts → manifest-types-jrBdBrES.d.ts} +29 -8
- package/dist/manifest.d.ts +2 -2
- package/dist/native.js +57 -57
- package/dist/runtime.d.ts +1 -1
- package/dist/worker/buildings-3d-worker.global.js +1 -0
- package/docs/authoring-model.md +8 -3
- package/docs/boundary-areas-and-choropleths.md +217 -0
- package/docs/buildings-3d.md +102 -21
- package/docs/cartographic-defaults.md +142 -0
- package/docs/data-is-separate-from-design.md +54 -2
- package/docs/hosted-session-authorization.md +2 -4
- package/docs/map-and-terrain-examples.md +10 -3
- package/docs/street-lights.md +22 -6
- package/docs/sun-and-light.md +42 -2
- package/docs/themes-and-module-styles.md +66 -0
- package/docs/trees-3d.md +22 -9
- package/package.json +2 -2
package/dist/browser.js
CHANGED
|
@@ -933,9 +933,9 @@ var init_sun = __esm({
|
|
|
933
933
|
azimuth: 210,
|
|
934
934
|
altitude: 60,
|
|
935
935
|
color: "#ffffff",
|
|
936
|
-
intensity: 0.
|
|
936
|
+
intensity: 0.2,
|
|
937
937
|
shadowIntensity: 1,
|
|
938
|
-
ambient: Object.freeze({ color: "#ffffff", intensity: 0.
|
|
938
|
+
ambient: Object.freeze({ color: "#ffffff", intensity: 0.8 })
|
|
939
939
|
});
|
|
940
940
|
degrees = Math.PI / 180;
|
|
941
941
|
}
|
|
@@ -4466,6 +4466,159 @@ function withAlpha(color, alpha) {
|
|
|
4466
4466
|
return `rgba(${value >> 16}, ${value >> 8 & 255}, ${value & 255}, ${alpha})`;
|
|
4467
4467
|
}
|
|
4468
4468
|
|
|
4469
|
+
// src/boundary-areas/schema.ts
|
|
4470
|
+
var tileflowBoundaryClasses = [
|
|
4471
|
+
"country",
|
|
4472
|
+
"dependency",
|
|
4473
|
+
"region",
|
|
4474
|
+
"province_or_county",
|
|
4475
|
+
"municipality",
|
|
4476
|
+
"district",
|
|
4477
|
+
"neighbourhood"
|
|
4478
|
+
];
|
|
4479
|
+
var tileflowBoundaryClassTiers = ["primary", "secondary"];
|
|
4480
|
+
var tileflowBoundaryAreasV1Schema = Object.freeze({
|
|
4481
|
+
classes: tileflowBoundaryClasses,
|
|
4482
|
+
/** First zoom at which each class appears, by tier. */
|
|
4483
|
+
classMinzoom: Object.freeze({
|
|
4484
|
+
country: Object.freeze({ primary: 0, secondary: 0 }),
|
|
4485
|
+
dependency: Object.freeze({ primary: 0, secondary: 0 }),
|
|
4486
|
+
region: Object.freeze({ primary: 3, secondary: 3 }),
|
|
4487
|
+
province_or_county: Object.freeze({ primary: 6, secondary: 7 }),
|
|
4488
|
+
municipality: Object.freeze({ primary: 8, secondary: 8 }),
|
|
4489
|
+
district: Object.freeze({ primary: 10, secondary: 10 }),
|
|
4490
|
+
neighbourhood: Object.freeze({ primary: 11, secondary: 11 })
|
|
4491
|
+
}),
|
|
4492
|
+
fields: Object.freeze({
|
|
4493
|
+
adminDepth: "admin_depth",
|
|
4494
|
+
adminLevel: "admin_level",
|
|
4495
|
+
class: "class",
|
|
4496
|
+
classTier: "class_tier",
|
|
4497
|
+
iso31661: "iso_3166_1",
|
|
4498
|
+
iso31662: "iso_3166_2",
|
|
4499
|
+
name: "name",
|
|
4500
|
+
nuts: "nuts",
|
|
4501
|
+
ref: "ref",
|
|
4502
|
+
wikidata: "wikidata"
|
|
4503
|
+
}),
|
|
4504
|
+
layers: Object.freeze({ areas: "boundary_area", labels: "boundary_label" }),
|
|
4505
|
+
/** Properties present on `boundary_area` polygons. */
|
|
4506
|
+
areaProperties: Object.freeze([
|
|
4507
|
+
"class",
|
|
4508
|
+
"class_tier",
|
|
4509
|
+
"admin_level",
|
|
4510
|
+
"admin_depth",
|
|
4511
|
+
"ref",
|
|
4512
|
+
"iso_3166_1"
|
|
4513
|
+
]),
|
|
4514
|
+
maxzoom: 12,
|
|
4515
|
+
minzoom: 0,
|
|
4516
|
+
product: "boundaries-v1",
|
|
4517
|
+
schemaVersion: 1,
|
|
4518
|
+
tiers: tileflowBoundaryClassTiers
|
|
4519
|
+
});
|
|
4520
|
+
var tileflowBoundaryFamilies = Object.freeze({
|
|
4521
|
+
boundaries: Object.freeze({
|
|
4522
|
+
attribution: "\xA9 OpenStreetMap contributors",
|
|
4523
|
+
license: "ODbL-1.0",
|
|
4524
|
+
path: "/tiles/boundaries/tiles.json",
|
|
4525
|
+
sourceId: "tileflow-boundaries"
|
|
4526
|
+
})
|
|
4527
|
+
});
|
|
4528
|
+
function isTileflowBoundaryFamily(value) {
|
|
4529
|
+
return typeof value === "string" && Object.hasOwn(tileflowBoundaryFamilies, value);
|
|
4530
|
+
}
|
|
4531
|
+
|
|
4532
|
+
// src/boundary-areas/feature-state.ts
|
|
4533
|
+
function createBoundaryFeatureState(map, options = {}) {
|
|
4534
|
+
const family = options.family ?? "boundaries";
|
|
4535
|
+
if (!isTileflowBoundaryFamily(family)) {
|
|
4536
|
+
throw new Error(`Unknown Tileflow Boundaries family: ${String(family)}.`);
|
|
4537
|
+
}
|
|
4538
|
+
const source = tileflowBoundaryFamilies[family].sourceId;
|
|
4539
|
+
const stateKey = options.state?.trim() || "value";
|
|
4540
|
+
const sourceLayers = [
|
|
4541
|
+
tileflowBoundaryAreasV1Schema.layers.areas,
|
|
4542
|
+
...options.labels ? [tileflowBoundaryAreasV1Schema.layers.labels] : []
|
|
4543
|
+
];
|
|
4544
|
+
let applied = /* @__PURE__ */ new Map();
|
|
4545
|
+
let disposed = false;
|
|
4546
|
+
const write = (id, value) => {
|
|
4547
|
+
for (const sourceLayer of sourceLayers) {
|
|
4548
|
+
map.setFeatureState({ id, source, sourceLayer }, { [stateKey]: value });
|
|
4549
|
+
}
|
|
4550
|
+
};
|
|
4551
|
+
const erase = (id) => {
|
|
4552
|
+
for (const sourceLayer of sourceLayers) {
|
|
4553
|
+
map.removeFeatureState({ id, source, sourceLayer }, stateKey);
|
|
4554
|
+
}
|
|
4555
|
+
};
|
|
4556
|
+
const sourceReady = () => map.getSource(source) !== void 0;
|
|
4557
|
+
let pendingReload = false;
|
|
4558
|
+
const onStyleData = () => {
|
|
4559
|
+
if (!sourceReady()) {
|
|
4560
|
+
pendingReload = true;
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
if (!pendingReload) return;
|
|
4564
|
+
pendingReload = false;
|
|
4565
|
+
for (const [id, value] of applied) write(id, value);
|
|
4566
|
+
};
|
|
4567
|
+
map.on("styledata", onStyleData);
|
|
4568
|
+
const assertOpen = () => {
|
|
4569
|
+
if (disposed) throw new Error("This Boundaries feature-state controller was disposed.");
|
|
4570
|
+
};
|
|
4571
|
+
return Object.freeze({
|
|
4572
|
+
clear() {
|
|
4573
|
+
assertOpen();
|
|
4574
|
+
if (sourceReady()) for (const id of applied.keys()) erase(id);
|
|
4575
|
+
applied = /* @__PURE__ */ new Map();
|
|
4576
|
+
},
|
|
4577
|
+
dispose() {
|
|
4578
|
+
if (disposed) return;
|
|
4579
|
+
if (sourceReady()) for (const id of applied.keys()) erase(id);
|
|
4580
|
+
applied = /* @__PURE__ */ new Map();
|
|
4581
|
+
map.off("styledata", onStyleData);
|
|
4582
|
+
disposed = true;
|
|
4583
|
+
},
|
|
4584
|
+
set(values) {
|
|
4585
|
+
assertOpen();
|
|
4586
|
+
const featureKey = promotedKey(map, source) ?? "id";
|
|
4587
|
+
if (values.joinBy !== featureKey) {
|
|
4588
|
+
throw new Error(
|
|
4589
|
+
`Boundaries feature-state is keyed by ${featureKey}; join the rows to ${featureKey} first with joinBoundaryRows.`
|
|
4590
|
+
);
|
|
4591
|
+
}
|
|
4592
|
+
const next = /* @__PURE__ */ new Map();
|
|
4593
|
+
for (const row of values.rows) {
|
|
4594
|
+
if (row.value === null) continue;
|
|
4595
|
+
if (featureKey === "id") {
|
|
4596
|
+
if (typeof row.key !== "number" || !Number.isSafeInteger(row.key) || row.key < 0) {
|
|
4597
|
+
throw new Error("Boundaries feature-state rows keyed by id need integer keys.");
|
|
4598
|
+
}
|
|
4599
|
+
} else if (typeof row.key !== "string" || !row.key) {
|
|
4600
|
+
throw new Error("Boundaries feature-state rows keyed by ref need string keys.");
|
|
4601
|
+
}
|
|
4602
|
+
next.set(row.key, row.value);
|
|
4603
|
+
}
|
|
4604
|
+
if (sourceReady()) {
|
|
4605
|
+
for (const id of applied.keys()) if (!next.has(id)) erase(id);
|
|
4606
|
+
for (const [id, value] of next) if (applied.get(id) !== value) write(id, value);
|
|
4607
|
+
} else {
|
|
4608
|
+
pendingReload = true;
|
|
4609
|
+
}
|
|
4610
|
+
applied = next;
|
|
4611
|
+
}
|
|
4612
|
+
});
|
|
4613
|
+
}
|
|
4614
|
+
function promotedKey(map, source) {
|
|
4615
|
+
const definition = map.getStyle?.()?.sources?.[source];
|
|
4616
|
+
const promoteId = definition?.promoteId;
|
|
4617
|
+
if (promoteId === void 0) return void 0;
|
|
4618
|
+
const value = typeof promoteId === "string" ? promoteId : promoteId[tileflowBoundaryAreasV1Schema.layers.areas];
|
|
4619
|
+
return value === "ref" ? "ref" : void 0;
|
|
4620
|
+
}
|
|
4621
|
+
|
|
4469
4622
|
// src/line-progress.ts
|
|
4470
4623
|
var epsilon = 5e-4;
|
|
4471
4624
|
var transparent = "rgba(0, 0, 0, 0)";
|
|
@@ -4808,13 +4961,14 @@ var tileflowDefaultBuildings3DDesign = Object.freeze({
|
|
|
4808
4961
|
layers: Object.freeze({ building: "building", places: "poi" }),
|
|
4809
4962
|
color: "#d8d0c0",
|
|
4810
4963
|
edges: 0,
|
|
4811
|
-
|
|
4964
|
+
edgeRadius: 0.8,
|
|
4965
|
+
lighting: "sun",
|
|
4812
4966
|
colorVariation: 0,
|
|
4813
4967
|
verticalGradient: true,
|
|
4814
4968
|
nightLight: 0,
|
|
4815
4969
|
windows: false,
|
|
4816
|
-
shadows:
|
|
4817
|
-
occlusion:
|
|
4970
|
+
shadows: true,
|
|
4971
|
+
occlusion: true
|
|
4818
4972
|
});
|
|
4819
4973
|
function readTileflowBuildings3D(metadata) {
|
|
4820
4974
|
if (!isRecord6(metadata)) return void 0;
|
|
@@ -4825,6 +4979,7 @@ function readTileflowBuildings3D(metadata) {
|
|
|
4825
4979
|
return void 0;
|
|
4826
4980
|
if (typeof color !== "string" && !Array.isArray(color)) return void 0;
|
|
4827
4981
|
const within = (number, high, fallback) => typeof number === "number" && number >= 0 && number <= high ? number : fallback;
|
|
4982
|
+
const defaults = tileflowDefaultBuildings3DDesign;
|
|
4828
4983
|
return {
|
|
4829
4984
|
source,
|
|
4830
4985
|
layers: {
|
|
@@ -4833,14 +4988,15 @@ function readTileflowBuildings3D(metadata) {
|
|
|
4833
4988
|
},
|
|
4834
4989
|
color,
|
|
4835
4990
|
edges: within(value.edges, 1, 0),
|
|
4836
|
-
|
|
4991
|
+
edgeRadius: within(value.edgeRadius, 1, defaults.edgeRadius),
|
|
4992
|
+
lighting: value.lighting === "style" ? "style" : defaults.lighting,
|
|
4837
4993
|
colorVariation: within(value.colorVariation, 1, 0),
|
|
4838
4994
|
verticalGradient: value.verticalGradient !== false,
|
|
4839
4995
|
...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
|
|
4840
4996
|
nightLight: within(value.nightLight, 2, 0),
|
|
4841
4997
|
windows: value.windows === true,
|
|
4842
|
-
shadows: value.shadows ===
|
|
4843
|
-
occlusion: value.occlusion ===
|
|
4998
|
+
shadows: typeof value.shadows === "boolean" ? value.shadows : defaults.shadows,
|
|
4999
|
+
occlusion: typeof value.occlusion === "boolean" ? value.occlusion : defaults.occlusion,
|
|
4844
5000
|
...modelsOf(value.models),
|
|
4845
5001
|
...modelColorsOf(value.modelColors),
|
|
4846
5002
|
...landmarksOf(value.landmarks)
|
|
@@ -5829,10 +5985,12 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5829
5985
|
const scale = tileflowBuildingExtent / layer.extent;
|
|
5830
5986
|
const metresPerUnit = options.tileMetres / tileflowBuildingExtent;
|
|
5831
5987
|
const fallback = options.defaultHeight ?? 5;
|
|
5988
|
+
const edgeRadius = Number.isFinite(options.edgeRadius) ? Math.max(0, Math.min(1, options.edgeRadius)) / metresPerUnit : 0;
|
|
5832
5989
|
const vertices = new VertexWriter();
|
|
5833
5990
|
const roofs = new IndexWriter();
|
|
5834
5991
|
const walls = new IndexWriter();
|
|
5835
5992
|
const features = [];
|
|
5993
|
+
const parts = [];
|
|
5836
5994
|
let tallest = 0;
|
|
5837
5995
|
const uses = nightUsesOf(layer, options);
|
|
5838
5996
|
for (const [featureIndex, feature] of layer.features.entries()) {
|
|
@@ -5848,16 +6006,35 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5848
6006
|
const index = features.length;
|
|
5849
6007
|
let used = false;
|
|
5850
6008
|
for (const polygon of polygonsOf(feature.geometry, scale)) {
|
|
5851
|
-
|
|
6009
|
+
let rings = polygon.map(clip).filter((ring) => ring.length >= 6);
|
|
5852
6010
|
if (rings.length === 0 || area2(rings[0]) === 0) continue;
|
|
5853
6011
|
const [cx, cy] = centroid(rings[0]);
|
|
5854
6012
|
if (options.masks?.length && (tileflowInsideRings(options.masks, cx, cy) || tileflowRingsOverlap(rings, options.masks, maskMargin / metresPerUnit)))
|
|
5855
6013
|
continue;
|
|
6014
|
+
const part = {
|
|
6015
|
+
cell: Math.min(7, Math.max(0, Math.floor(cy / 1024))) * 8 + Math.min(7, Math.max(0, Math.floor(cx / 1024))),
|
|
6016
|
+
bounds: [Infinity, Infinity, -Infinity, -Infinity, height + 0.1],
|
|
6017
|
+
roofsFrom: roofs.length,
|
|
6018
|
+
roofsCount: 0,
|
|
6019
|
+
wallsFrom: walls.length,
|
|
6020
|
+
wallsCount: 0
|
|
6021
|
+
};
|
|
6022
|
+
for (const ring of rings)
|
|
6023
|
+
for (let at = 0; at < ring.length; at += 2) {
|
|
6024
|
+
part.bounds[0] = Math.min(part.bounds[0], ring[at] - 1);
|
|
6025
|
+
part.bounds[1] = Math.min(part.bounds[1], ring[at + 1] - 1);
|
|
6026
|
+
part.bounds[2] = Math.max(part.bounds[2], ring[at] + 1);
|
|
6027
|
+
part.bounds[3] = Math.max(part.bounds[3], ring[at + 1] + 1);
|
|
6028
|
+
}
|
|
5856
6029
|
used = true;
|
|
5857
6030
|
tallest = Math.max(tallest, height);
|
|
5858
6031
|
const seed = hash(cx * 0.0731 + cy * 0.19137 + index * 1.618);
|
|
5859
6032
|
const jitter = rings[0].some((value) => value <= 0 || value >= tileflowBuildingExtent) ? hash(height * 7.31 + base * 3.17 + tone) : seed;
|
|
5860
6033
|
const building = { base, top: height, jitter, seed, tone, group, use, cx, cy, index };
|
|
6034
|
+
const rounded = rings.map(
|
|
6035
|
+
(ring, at) => roundCorners(ring, at, edgeRadius, options.cornerAngle)
|
|
6036
|
+
);
|
|
6037
|
+
rings = rounded.map(({ ring }) => ring);
|
|
5861
6038
|
const roof = polygon.length === 1 && rings.length === 1 ? tileflowRoofOf(
|
|
5862
6039
|
polygon[0],
|
|
5863
6040
|
rings[0],
|
|
@@ -5869,6 +6046,7 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5869
6046
|
) : void 0;
|
|
5870
6047
|
if (roof) {
|
|
5871
6048
|
tallest = Math.max(tallest, roof.ridge);
|
|
6049
|
+
part.bounds[4] = Math.max(part.bounds[4], roof.ridge + 0.1);
|
|
5872
6050
|
writeRoof(roof, building, vertices, roofs);
|
|
5873
6051
|
} else {
|
|
5874
6052
|
writeFlatRoof(rings, building, vertices, roofs);
|
|
@@ -5902,6 +6080,14 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5902
6080
|
if (length === 0 || onBorder(ax, ay, bx, by)) continue;
|
|
5903
6081
|
const normalX = -(by - ay) / length * outward;
|
|
5904
6082
|
const normalY = (bx - ax) / length * outward;
|
|
6083
|
+
const startNormal = rounded[ringIndex].normals[at] ?? [normalX, normalY];
|
|
6084
|
+
const endNormal = rounded[ringIndex].normals[(at + 1) % points] ?? [normalX, normalY];
|
|
6085
|
+
const normalAt = (share3) => {
|
|
6086
|
+
const x3 = startNormal[0] + (endNormal[0] - startNormal[0]) * share3;
|
|
6087
|
+
const y2 = startNormal[1] + (endNormal[1] - startNormal[1]) * share3;
|
|
6088
|
+
const length2 = Math.hypot(x3, y2) || 1;
|
|
6089
|
+
return [x3 / length2, y2 / length2];
|
|
6090
|
+
};
|
|
5905
6091
|
const shares = [0, ...roof ? roof.breaks(ax, ay, bx, by) : [], 1];
|
|
5906
6092
|
for (let piece = 0; piece + 1 < shares.length; piece += 1) {
|
|
5907
6093
|
const first = piece === 0;
|
|
@@ -5914,6 +6100,8 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5914
6100
|
const corners = (first && tight(at) ? 1 : 0) | (last && tight(at + 1) ? 2 : 0) | (ringIndex > 0 ? 4 : 0);
|
|
5915
6101
|
const cut = (first && onEdge(ax, ay) ? 1 : 0) | (last && onEdge(bx, by) ? 2 : 0);
|
|
5916
6102
|
const end = along + metres;
|
|
6103
|
+
const sn = normalAt(shares[piece]);
|
|
6104
|
+
const en = normalAt(shares[piece + 1]);
|
|
5917
6105
|
const startTop = topAt(sx, sy);
|
|
5918
6106
|
const endTop = topAt(ex, ey);
|
|
5919
6107
|
const v2 = vertices.write(
|
|
@@ -5921,8 +6109,8 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5921
6109
|
sx,
|
|
5922
6110
|
sy,
|
|
5923
6111
|
0,
|
|
5924
|
-
|
|
5925
|
-
|
|
6112
|
+
sn[0],
|
|
6113
|
+
sn[1],
|
|
5926
6114
|
1,
|
|
5927
6115
|
along,
|
|
5928
6116
|
corners,
|
|
@@ -5936,8 +6124,8 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5936
6124
|
ex,
|
|
5937
6125
|
ey,
|
|
5938
6126
|
0,
|
|
5939
|
-
|
|
5940
|
-
|
|
6127
|
+
en[0],
|
|
6128
|
+
en[1],
|
|
5941
6129
|
1,
|
|
5942
6130
|
end,
|
|
5943
6131
|
corners,
|
|
@@ -5951,8 +6139,8 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5951
6139
|
sx,
|
|
5952
6140
|
sy,
|
|
5953
6141
|
1,
|
|
5954
|
-
|
|
5955
|
-
|
|
6142
|
+
sn[0],
|
|
6143
|
+
sn[1],
|
|
5956
6144
|
1,
|
|
5957
6145
|
along,
|
|
5958
6146
|
corners,
|
|
@@ -5966,8 +6154,8 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5966
6154
|
ex,
|
|
5967
6155
|
ey,
|
|
5968
6156
|
1,
|
|
5969
|
-
|
|
5970
|
-
|
|
6157
|
+
en[0],
|
|
6158
|
+
en[1],
|
|
5971
6159
|
1,
|
|
5972
6160
|
end,
|
|
5973
6161
|
corners,
|
|
@@ -5981,14 +6169,44 @@ function meshTileflowBuildings(layer, options) {
|
|
|
5981
6169
|
}
|
|
5982
6170
|
}
|
|
5983
6171
|
}
|
|
6172
|
+
part.roofsCount = roofs.length - part.roofsFrom;
|
|
6173
|
+
part.wallsCount = walls.length - part.wallsFrom;
|
|
6174
|
+
parts.push(part);
|
|
5984
6175
|
}
|
|
5985
6176
|
if (used) features.push(properties);
|
|
5986
6177
|
}
|
|
5987
6178
|
if (vertices.count === 0) return void 0;
|
|
5988
6179
|
const indices = new Uint32Array(roofs.length + walls.length);
|
|
5989
|
-
|
|
5990
|
-
|
|
6180
|
+
const groups = [];
|
|
6181
|
+
let roofAt = 0;
|
|
6182
|
+
let wallAt = roofs.length;
|
|
6183
|
+
const roofIndices = roofs.values();
|
|
6184
|
+
const wallIndices = walls.values();
|
|
6185
|
+
parts.sort((a, b2) => a.cell - b2.cell);
|
|
6186
|
+
let cell = -1;
|
|
6187
|
+
for (const part of parts) {
|
|
6188
|
+
if (part.cell !== cell) {
|
|
6189
|
+
cell = part.cell;
|
|
6190
|
+
groups.push({
|
|
6191
|
+
bounds: [...part.bounds],
|
|
6192
|
+
roofsFrom: roofAt,
|
|
6193
|
+
roofsCount: 0,
|
|
6194
|
+
wallsFrom: wallAt,
|
|
6195
|
+
wallsCount: 0
|
|
6196
|
+
});
|
|
6197
|
+
}
|
|
6198
|
+
const group = groups.at(-1);
|
|
6199
|
+
for (let axis = 0; axis < 5; axis += 1)
|
|
6200
|
+
group.bounds[axis] = axis < 2 ? Math.min(group.bounds[axis], part.bounds[axis]) : Math.max(group.bounds[axis], part.bounds[axis]);
|
|
6201
|
+
indices.set(roofIndices.subarray(part.roofsFrom, part.roofsFrom + part.roofsCount), roofAt);
|
|
6202
|
+
indices.set(wallIndices.subarray(part.wallsFrom, part.wallsFrom + part.wallsCount), wallAt);
|
|
6203
|
+
roofAt += part.roofsCount;
|
|
6204
|
+
wallAt += part.wallsCount;
|
|
6205
|
+
group.roofsCount += part.roofsCount;
|
|
6206
|
+
group.wallsCount += part.wallsCount;
|
|
6207
|
+
}
|
|
5991
6208
|
return {
|
|
6209
|
+
groups,
|
|
5992
6210
|
vertices: vertices.bytes(),
|
|
5993
6211
|
indices,
|
|
5994
6212
|
wallsFrom: roofs.length,
|
|
@@ -6075,6 +6293,68 @@ function writeRoof(roof, building, vertices, roofs) {
|
|
|
6075
6293
|
}
|
|
6076
6294
|
}
|
|
6077
6295
|
}
|
|
6296
|
+
function roundCorners(ring, ringIndex, radius, cornerAngle = Math.PI / 6) {
|
|
6297
|
+
if (radius < 1) return { ring, normals: [] };
|
|
6298
|
+
const points = ring.length / 2;
|
|
6299
|
+
const left = area2(ring) > 0;
|
|
6300
|
+
const outward = ringIndex === 0 ? left ? -1 : 1 : left ? 1 : -1;
|
|
6301
|
+
const result = [];
|
|
6302
|
+
const normals = [];
|
|
6303
|
+
const append = (x3, y2, normal) => {
|
|
6304
|
+
x3 = clampUnit(x3);
|
|
6305
|
+
y2 = clampUnit(y2);
|
|
6306
|
+
if (result.at(-2) === x3 && result.at(-1) === y2) return;
|
|
6307
|
+
result.push(x3, y2);
|
|
6308
|
+
normals.push(normal);
|
|
6309
|
+
};
|
|
6310
|
+
for (let at = 0; at < points; at += 1) {
|
|
6311
|
+
const before = (at + points - 1) % points;
|
|
6312
|
+
const after = (at + 1) % points;
|
|
6313
|
+
const [px, py] = [ring[before * 2], ring[before * 2 + 1]];
|
|
6314
|
+
const [x3, y2] = [ring[at * 2], ring[at * 2 + 1]];
|
|
6315
|
+
const [nx, ny] = [ring[after * 2], ring[after * 2 + 1]];
|
|
6316
|
+
const incoming = Math.hypot(x3 - px, y2 - py);
|
|
6317
|
+
const outgoing = Math.hypot(nx - x3, ny - y2);
|
|
6318
|
+
if (!incoming || !outgoing || onEdge(x3, y2) || onBorder(px, py, x3, y2) || onBorder(x3, y2, nx, ny)) {
|
|
6319
|
+
append(x3, y2);
|
|
6320
|
+
continue;
|
|
6321
|
+
}
|
|
6322
|
+
const [ix, iy] = [(x3 - px) / incoming, (y2 - py) / incoming];
|
|
6323
|
+
const [ox, oy] = [(nx - x3) / outgoing, (ny - y2) / outgoing];
|
|
6324
|
+
const turn = Math.atan2(ix * oy - iy * ox, ix * ox + iy * oy);
|
|
6325
|
+
if (turn * outward >= -0.08 || Math.abs(turn) > Math.PI * 0.85) {
|
|
6326
|
+
append(x3, y2);
|
|
6327
|
+
continue;
|
|
6328
|
+
}
|
|
6329
|
+
const tangent = Math.tan(Math.abs(turn) / 2);
|
|
6330
|
+
const reach = Math.min(radius * tangent, incoming * 0.2, outgoing * 0.2);
|
|
6331
|
+
const actualRadius = reach / tangent;
|
|
6332
|
+
if (actualRadius < 1) {
|
|
6333
|
+
append(x3, y2);
|
|
6334
|
+
continue;
|
|
6335
|
+
}
|
|
6336
|
+
const [normalX, normalY] = [-iy * outward, ix * outward];
|
|
6337
|
+
const [cx, cy] = [
|
|
6338
|
+
x3 - ix * reach - normalX * actualRadius,
|
|
6339
|
+
y2 - iy * reach - normalY * actualRadius
|
|
6340
|
+
];
|
|
6341
|
+
const start = Math.atan2(normalY, normalX);
|
|
6342
|
+
const steps = Math.max(
|
|
6343
|
+
cornerAngle <= Math.PI / 6 ? 2 : 1,
|
|
6344
|
+
Math.ceil(Math.abs(turn) / Math.max(Math.PI / 6, Math.min(Math.PI / 3, cornerAngle)))
|
|
6345
|
+
);
|
|
6346
|
+
for (let step = 0; step <= steps; step += 1) {
|
|
6347
|
+
const angle = start + turn * step / steps;
|
|
6348
|
+
const normal = [Math.cos(angle), Math.sin(angle)];
|
|
6349
|
+
append(cx + normal[0] * actualRadius, cy + normal[1] * actualRadius, normal);
|
|
6350
|
+
}
|
|
6351
|
+
}
|
|
6352
|
+
if (result[0] === result.at(-2) && result[1] === result.at(-1)) {
|
|
6353
|
+
result.splice(-2);
|
|
6354
|
+
normals.pop();
|
|
6355
|
+
}
|
|
6356
|
+
return result.length >= 6 ? { ring: result, normals } : { ring, normals: [] };
|
|
6357
|
+
}
|
|
6078
6358
|
var VertexWriter = class {
|
|
6079
6359
|
#buffer = new ArrayBuffer(4096 * tileflowBuildingVertexBytes);
|
|
6080
6360
|
#view = new DataView(this.#buffer);
|
|
@@ -6293,6 +6573,330 @@ function hash(value) {
|
|
|
6293
6573
|
return s - Math.floor(s);
|
|
6294
6574
|
}
|
|
6295
6575
|
|
|
6576
|
+
// src/buildings-3d-mesher.ts
|
|
6577
|
+
function browserWorker() {
|
|
6578
|
+
if (typeof Worker === "undefined" || false)
|
|
6579
|
+
return;
|
|
6580
|
+
const url = URL.createObjectURL(
|
|
6581
|
+
new Blob(['"use strict";(()=>{var An=new Set,Dn=!1;function kn(n,e,t=2){let s=e&&e.length,o=s?e[0]*t:n.length;An.size&&An.clear();let r=he(n,0,o,t,!0),i=[];if(!r||r.next===r.prev)return i;let u=0,l=0,c=0;if(s&&(r=_e(n,e,r,t)),n.length>80*t){u=n[0],l=n[1];let a=u,f=l;for(let h=t;h<o;h+=t){let m=n[h],b=n[h+1];m<u&&(u=m),b<l&&(l=b),m>a&&(a=m),b>f&&(f=b)}c=Math.max(a-u,f-l),c=c!==0?32767/c:0}return Wn(r,i,u,l,c),i}function he(n,e,t,s,o){let r=null;if(o===Ye(n,e,t,s)>0)for(let i=e;i<t;i+=s)r=fe(i/s|0,n[i],n[i+1],r);else for(let i=t-s;i>=e;i-=s)r=fe(i/s|0,n[i],n[i+1],r);return r&&In(r,r.next)&&(Tn(r),r=r.next),r}function fn(n,e=n){let t=e===n,s=n,o;do o=!1,s!==s.next&&(An.size===0||!An.has(s))&&(In(s,s.next)||O(s.prev,s,s.next)===0)?((t||s===e)&&(e=s.prev),Dn=!0,Tn(s),s=s.prev,o=!0):(t||s!==e)&&(s=s.next,o=!t);while(o||s!==e);return e}function Wn(n,e,t,s,o){o&&Se(n,t,s,o);let r=n,i=!1;for(;n.prev!==n.next;){let u=n.prev,l=n.next;if(O(u,n,l)<0&&(o?ke(n,t,s,o):Te(n))){e.push(u.i,n.i,l.i),Tn(n),n=l,r=l;continue}if(n=l,n===r){if(Dn=!1,n=fn(n),Dn){r=n;continue}if(!i){n=Be(n,e),r=n,i=!0;continue}Fe(n,e,t,s,o);break}}}function Te(n){let e=n.prev,t=n,s=n.next,o=e.x,r=t.x,i=s.x,u=e.y,l=t.y,c=s.y,a=Math.min(o,r,i),f=Math.min(u,l,c),h=Math.max(o,r,i),m=Math.max(u,l,c),b=s.next;for(;b!==e;){if(b.x>=a&&b.x<=h&&b.y>=f&&b.y<=m&&!(o===b.x&&u===b.y)&&Vn(o,u,r,l,i,c,b.x,b.y)&&O(b.prev,b,b.next)>=0)return!1;b=b.next}return!0}function ke(n,e,t,s){let o=n.prev,r=n,i=n.next,u=o.x,l=r.x,c=i.x,a=o.y,f=r.y,h=i.y,m=Math.min(u,l,c),b=Math.min(a,f,h),d=Math.max(u,l,c),x=Math.max(a,f,h),I=$n(m,b,e,t,s),B=$n(d,x,e,t,s),M=n.prevZ;for(;M&&M.z>=I;){if(M.x>=m&&M.x<=d&&M.y>=b&&M.y<=x&&M!==i&&!(u===M.x&&a===M.y)&&Vn(u,a,l,f,c,h,M.x,M.y)&&O(M.prev,M,M.next)>=0)return!1;M=M.prevZ}let y=n.nextZ;for(;y&&y.z<=B;){if(y.x>=m&&y.x<=d&&y.y>=b&&y.y<=x&&y!==i&&!(u===y.x&&a===y.y)&&Vn(u,a,l,f,c,h,y.x,y.y)&&O(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Be(n,e){let t=n,s=!1;do{let o=t.prev,r=t.next.next;xe(o,t,t.next,r,!1)&&Un(o,r)&&Un(r,o)&&(e.push(o.i,t.i,r.i),Tn(t),Tn(t.next),t=n=r,s=!0),t=t.next}while(t!==n);return s?fn(t):t}function Fe(n,e,t,s,o){let r=n;do{let i=r.next.next;for(;i!==r.prev;){if(r.i!==i.i&&He(r,i)){let u=de(r,i);r=fn(r,r.next),u=fn(u,u.next),Wn(r,e,t,s,o),Wn(u,e,t,s,o);return}i=i.next}r=r.next}while(r!==n)}var Kn=!1;function _e(n,e,t,s){let o=[];for(let r=0,i=e.length;r<i;r++){let u=e[r]*s,l=r<i-1?e[r+1]*s:n.length,c=he(n,u,l,s,!1);c===c.next&&An.add(c),o.push(Ee(c))}o.sort(Pe),Le(n.length/s,e.length),be(t,t),Kn=!0;for(let r=0;r<o.length;r++)t=Oe(o[r],t);return Kn=!1,fn(t)}function Pe(n,e){return n.x-e.x||n.y-e.y||(n.next.y-n.y)/(n.next.x-n.x)-(e.next.y-e.y)/(e.next.x-e.x)}function Oe(n,e){let t=ze(n,e);if(!t)return e;let s=de(t,n),o=s.next;return be(t,o.next),fn(s,s.next),fn(t,t.next)}var me=16,_=new Float64Array(0),Nn=0,Jn=[],Qn=[];function Le(n,e){let t=Math.ceil((n+2*e)/me)+e+2;_.length<t*4&&(_=new Float64Array(t*4)),Nn=0}function be(n,e){let t=n;do{let s=Nn++;Jn[s]=t;let o=t.x,r=t.y,i=t.x,u=t.y,l=0;do{let a=t.next;t.z=s,a.x<o&&(o=a.x),a.x>i&&(i=a.x),a.y<r&&(r=a.y),a.y>u&&(u=a.y),t=a}while(++l<me&&t!==e);Qn[s]=t;let c=s*4;_[c]=o,_[c+1]=r,_[c+2]=i,_[c+3]=u}while(t!==e)}function Ce(n,e){let t=n.z*4;e.x<_[t]&&(_[t]=e.x),e.y<_[t+1]&&(_[t+1]=e.y),e.x>_[t+2]&&(_[t+2]=e.x),e.y>_[t+3]&&(_[t+3]=e.y)}function ce(n){let e=Qn[n];for(;e.prev.next!==e;)e=e.next;return Qn[n]=e,e}function ae(n){let e=Jn[n];for(;e.prev.next!==e;)e=e.next;return Jn[n]=e,e}function ze(n,e){let t=e,s=n.x,o=n.y,r=-1/0,i;if(In(n,t))return t;for(let h=0,m=0;h<Nn;h++,m+=4){if(o<_[m+1]||o>_[m+3]||_[m]>s||_[m+2]<=r)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t){if(In(n,t.next))return t.next;if(o<=t.y&&o>=t.next.y&&t.next.y!==t.y){let d=t.x+(o-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(d<=s&&d>r&&(r=d,i=t.x<t.next.x?t:t.next,d===s))return i}}t=t.next}while(t!==b)}if(!i)return null;let u=i.x,l=i.y,c=Math.min(o,l),a=Math.max(o,l),f=1/0;for(let h=0,m=0;h<Nn;h++,m+=4){if(_[m+2]<u||_[m]>s||_[m+3]<c||_[m+1]>a)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t&&s>=t.x&&t.x>=u&&s!==t.x&&Vn(o<l?s:r,o,u,l,o<l?r:s,o,t.x,t.y)){let d=Math.abs(o-t.y)/(s-t.x);(Un(t,n)||t.y===o&&t.next.y===o&&t.next.x>s)&&(d<f||d===f&&(t.x>i.x||t.x===i.x&&Ze(i,t)))&&(i=t,f=d)}t=t.next}while(t!==b)}return i}function Ze(n,e){return O(n.prev,n,e.prev)<0&&O(e.next,n,n.next)<0}var K=[],dn=[],an=new Uint32Array(0),vn=new Uint32Array(0),Rn=new Uint32Array(256);function Se(n,e,t,s){let o=n,r=0;do o.z=$n(o.x,o.y,e,t,s),K[r++]=o,o=o.next;while(o!==n);je(r);let i=null;for(let u=0;u<r;u++){let l=K[u];l.prevZ=i,i&&(i.nextZ=l),i=l}i.nextZ=null,K.fill(null,0,r),dn.fill(null,0,r)}function je(n){if(n<=32){for(let e=1;e<n;e++){let t=K[e],s=t.z,o=e-1;for(;o>=0&&K[o].z>s;)K[o+1]=K[o],o--;K[o+1]=t}return}an.length<n&&(an=new Uint32Array(n),vn=new Uint32Array(n),dn=new Array(n));for(let e=0;e<n;e++)an[e]=K[e].z;En(n,K,an,dn,vn,0),En(n,dn,vn,K,an,8),En(n,K,an,dn,vn,16),En(n,dn,vn,K,an,24)}function En(n,e,t,s,o,r){Rn.fill(0);for(let u=0;u<n;u++)Rn[t[u]>>>r&255]++;let i=0;for(let u=0;u<256;u++){let l=Rn[u];Rn[u]=i,i+=l}for(let u=0;u<n;u++){let l=t[u],c=Rn[l>>>r&255]++;s[c]=e[u],o[c]=l}}function $n(n,e,t,s,o){return n=(n-t)*o|0,e=(e-s)*o|0,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n|e<<1}function Ee(n){let e=n,t=n;do(e.x<t.x||e.x===t.x&&e.y<t.y)&&(t=e),e=e.next;while(e!==n);return t}function Vn(n,e,t,s,o,r,i,u){return(o-i)*(e-u)>=(n-i)*(r-u)&&(n-i)*(s-u)>=(t-i)*(e-u)&&(t-i)*(r-u)>=(o-i)*(s-u)}function He(n,e){let t=In(n,e)&&O(n.prev,n,n.next)>0&&O(e.prev,e,e.next)>0;return n.next.i!==e.i&&(t||Un(n,e)&&Un(e,n)&&(O(n.prev,n,e.prev)!==0||O(n,e.prev,e)!==0))&&!Ne(n,e)&&(t||Ve(n,e))}function O(n,e,t){return(e.y-n.y)*(t.x-e.x)-(e.x-n.x)*(t.y-e.y)}function In(n,e){return n.x===e.x&&n.y===e.y}function xe(n,e,t,s,o=!0){let r=O(n,e,t),i=O(n,e,s),u=O(t,s,n),l=O(t,s,e);return(r>0&&i<0||r<0&&i>0)&&(u>0&&l<0||u<0&&l>0)?!0:o?!!(r===0&&Hn(n,t,e)||i===0&&Hn(n,s,e)||u===0&&Hn(t,n,s)||l===0&&Hn(t,e,s)):!1}function Hn(n,e,t){return e.x<=Math.max(n.x,t.x)&&e.x>=Math.min(n.x,t.x)&&e.y<=Math.max(n.y,t.y)&&e.y>=Math.min(n.y,t.y)}function Ne(n,e){let t=Math.min(n.x,e.x),s=Math.max(n.x,e.x),o=Math.min(n.y,e.y),r=Math.max(n.y,e.y),i=n;do{let u=i.next;if(i.x>s&&u.x>s||i.x<t&&u.x<t||i.y>r&&u.y>r||i.y<o&&u.y<o){i=u;continue}if(i.i!==n.i&&u.i!==n.i&&i.i!==e.i&&u.i!==e.i&&xe(i,u,n,e))return!0;i=u}while(i!==n);return!1}function Un(n,e){return O(n.prev,n,n.next)<0?O(n,e,n.next)>=0&&O(n,n.prev,e)>=0:O(n,e,n.prev)<0||O(n,n.next,e)<0}function Ve(n,e){let t=n,s=!1,o=(n.x+e.x)/2,r=(n.y+e.y)/2;do{let i=t.next;t.y>r!=i.y>r&&o<(i.x-t.x)*(r-t.y)/(i.y-t.y)+t.x&&(s=!s),t=i}while(t!==n);return s}function de(n,e){let t=qn(n.i,n.x,n.y),s=qn(e.i,e.x,e.y),o=n.next,r=e.prev;return n.next=e,e.prev=n,t.next=o,o.prev=t,s.next=t,t.prev=s,r.next=s,s.prev=r,s}function fe(n,e,t,s){let o=qn(n,e,t);return s?(o.next=s.next,o.prev=s,s.next.prev=o,s.next=o):(o.prev=o,o.next=o),o}function Tn(n){n.next.prev=n.prev,n.prev.next=n.next,n.prevZ&&(n.prevZ.nextZ=n.nextZ),n.nextZ&&(n.nextZ.prevZ=n.prevZ),Kn&&Ce(n.prev,n.next)}function qn(n,e,t){return{i:n,x:e,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Ye(n,e,t,s){let o=0;for(let r=e,i=t-s;r<t;r+=s)o+=(n[i]-n[r])*(n[r+1]+n[i+1]),i=r;return o}var Xe=.7,Ge=Math.tan(Math.PI/6),De=Math.tan(35*Math.PI/180),We=Math.tan(Math.PI/18),Bn=.65,Fn=.4;function Me(n,e,t,s,o,r,i){let u=String(t.roof_shape??"");if(!u||u==="flat"||u==="sawtooth"||qe(n,i))return;let l=Qe(n);if(!l||Math.abs(ne(n))<Xe*4*l.halfU*l.halfV)return;let c=l.u,a=l.v,f=l.halfU*r,h=l.halfV*r;f<h&&([c,a,f,h]=[a,c,h,f]);let m=ge(t.roof_direction),b=m===void 0?void 0:[Math.sin(m*Math.PI/180),-Math.cos(m*Math.PI/180)];(b!==void 0?Math.abs(b[0]*c[0]+b[1]*c[1])>Math.abs(b[0]*a[0]+b[1]*a[1]):t.roof_orientation==="across")&&u!=="skillion"&&([c,a,f,h]=[a,c,h,f]);let x=Je(u,f,h,b&&[pe(b,c),pe(b,a)]);if(!x)return;let I=Math.max(0,o-s),B=ge(t.roof_height),M,y;if(B!==void 0&&B>0)y=o,M=Math.max(s+Math.min(2,I/2),o-B);else{let p=Math.min(Ke(u,h),I*.6,30);t.height_source==="height"?(y=o,M=o-p):(M=o,y=o+p)}let k=y-M;if(!(k>=.3))return;let[R,N]=l.centre,F=(p,A)=>{let v=(p-R)*r,g=(A-N)*r;return[v*c[0]+g*c[1],v*a[0]+g*a[1]]},S=(p,A)=>[R+(p*c[0]+A*a[0])/r,N+(p*c[1]+A*a[1])/r],J=(p,A)=>{let v=1/0;for(let[g,E,H]of x)v=Math.min(v,g*p+E*A+H);return Math.max(0,v)},Q=(p,A)=>M+k*J(...F(p,A)),L=[];for(let p=0;p+1<e.length;p+=2)L.push(...F(e[p],e[p+1]));ne(L)<0&&nt(L);let C=1.01,$=[-f*C,-h*C,f*C,-h*C,f*C,h*C,-f*C,h*C],V=[];for(let[p,A]of x.entries()){let v=$;for(let[T,j]of x.entries()){if(T===p)continue;let z=A[0]-j[0],nn=A[1]-j[1],en=A[2]-j[2];if(Math.abs(z)+Math.abs(nn)+Math.abs(en)<1e-9){T<p&&(v=[]);continue}if(v=ye(v,z,nn,en),v.length<6)break}if(v.length<6)continue;let g=L,E=v.length/2;for(let T=0;T<E&&g.length>=6;T+=1){let[j,z]=[v[T*2],v[T*2+1]],[nn,en]=[v[(T+1)%E*2],v[(T+1)%E*2+1]];g=ye(g,en-z,j-nn,-(en-z)*j-(j-nn)*z)}if(g.length<6||Math.abs(ne(g))<1e-6)continue;let H=[];for(let T=0;T<g.length;T+=2)H.push(...S(g[T],g[T+1]));let q=kn(H);if(q.length===0)continue;let on=k*A[0],Y=k*A[1],w=on*c[0]+Y*a[0],U=on*c[1]+Y*a[1],rn=Math.hypot(w,U,1);V.push({ring:H,triangles:q,normal:[-w/rn,-U/rn]})}if(V.length!==0)return{eaves:M,ridge:y,faces:V,heightAt:Q,breaks(p,A,v,g){let[E,H]=F(p,A),[q,on]=F(v,g),Y=[];for(let w=0;w<x.length;w+=1)for(let U=w+1;U<x.length;U+=1){let rn=(Z,P)=>(x[w][0]-x[U][0])*Z+(x[w][1]-x[U][1])*P+(x[w][2]-x[U][2]),T=rn(E,H),j=rn(q,on);if(T===j||T*j>0)continue;let z=T/(T-j);if(z<=1e-4||z>=1-1e-4)continue;let nn=E+(q-E)*z,en=H+(on-H)*z;x[w][0]*nn+x[w][1]*en+x[w][2]<=J(nn,en)+1e-6&&Y.push(z)}return[...new Set(Y.map(w=>Math.round(w*1e6)/1e6))].sort((w,U)=>w-U)}}}function Ke(n,e){switch(n){case"skillion":return 2*e*We;case"pyramidal":return e*De;case"gambrel":case"mansard":return e*.8;case"dome":case"onion":case"round":return e;default:return e*Ge}}function Je(n,e,t,s){let o=e,r=t,i=[[0,-1/r,1],[0,1/r,1]],u=c=>c.flatMap(a=>[1,-1].flatMap(f=>{let h=a==="v"?r:o,m=Bn/(Fn*r)*f,b=(1-Bn)/((1-Fn)*r)*f,d=x=>a==="v"?[0,-x]:[-x,0];return[[...d(m),Bn/(Fn*r)*h],[...d(b),Bn+(1-Bn)/((1-Fn)*r)*(h-Fn*r)]]})),l=[.99,.8,.5,.2];switch(n){case"gabled":return i;case"saltbox":{let c=r/3;return[[0,-1/(r-c),1+c/(r-c)],[0,1/(r+c),1-c/(r+c)]]}case"hipped":return[...i,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...i,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...i,[-1/o,0,1],[1/o,0,1]];case"gambrel":return u(["v"]);case"mansard":return u(["v","u"]);case"skillion":{let[c,a]=s??[0,1],f=Math.abs(c)*o+Math.abs(a)*r;return f>0?[[-c/(2*f),-a/(2*f),.5]]:void 0}case"round":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return[1,-1].map(f=>[0,-f*c/(r*a),1/a])})];case"dome":case"onion":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return Array.from({length:12},(f,h)=>{let m=h*Math.PI/6;return[-c*Math.cos(m)/(o*a),-c*Math.sin(m)/(r*a),1/a]})})];default:return}}function ye(n,e,t,s){let o=n.length/2,r=[];for(let i=0;i<o;i+=1){let u=n[i*2],l=n[i*2+1],c=n[(i+1)%o*2],a=n[(i+1)%o*2+1],f=e*u+t*l+s,h=e*c+t*a+s;if(f<=0&&r.push(u,l),f<0&&h>0||f>0&&h<0){let m=f/(f-h);r.push(u+(c-u)*m,l+(a-l)*m)}}return r}function Qe(n){let e=$e(n);if(e.length<3)return;let t,s=1/0;for(let o=0;o<e.length;o+=1){let[r,i]=e[o],[u,l]=e[(o+1)%e.length],c=Math.hypot(u-r,l-i);if(c===0)continue;let a=[(u-r)/c,(l-i)/c],f=[-a[1],a[0]],[h,m,b,d]=[1/0,-1/0,1/0,-1/0];for(let[I,B]of e){let M=I*a[0]+B*a[1],y=I*f[0]+B*f[1];h=Math.min(h,M),m=Math.max(m,M),b=Math.min(b,y),d=Math.max(d,y)}let x=(m-h)*(d-b);if(x<s){s=x;let I=(h+m)/2,B=(b+d)/2;t={centre:[I*a[0]+B*f[0],I*a[1]+B*f[1]],u:a,v:f,halfU:(m-h)/2,halfV:(d-b)/2}}}return t&&t.halfU>0&&t.halfV>0?t:void 0}function $e(n){let e=[];for(let r=0;r+1<n.length;r+=2)e.push([n[r],n[r+1]]);e.sort((r,i)=>r[0]-i[0]||r[1]-i[1]);let t=(r,i,u)=>(i[0]-r[0])*(u[1]-r[1])-(i[1]-r[1])*(u[0]-r[0]),s=[];for(let r of e){for(;s.length>=2&&t(s.at(-2),s.at(-1),r)<=0;)s.pop();s.push(r)}let o=[];for(let r of e.reverse()){for(;o.length>=2&&t(o.at(-2),o.at(-1),r)<=0;)o.pop();o.push(r)}return[...s.slice(0,-1),...o.slice(0,-1)]}function qe(n,e){let[t,s,o,r]=[1/0,-1/0,1/0,-1/0];for(let u=0;u+1<n.length;u+=2)t=Math.min(t,n[u]),s=Math.max(s,n[u]),o=Math.min(o,n[u+1]),r=Math.max(r,n[u+1]);let i=n.length/2;for(let u=0;u<i;u+=1){let l=n[u*2],c=n[u*2+1],a=n[(u+1)%i*2],f=n[(u+1)%i*2+1];if(t<0&&l===t&&a===t||s>e&&l===s&&a===s||o<0&&c===o&&f===o||r>e&&c===r&&f===r)return!0}return!1}function ne(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e/2}function nt(n){let e=[];for(let t=0;t<n.length;t+=2)e.push([n[t],n[t+1]]);e.reverse(),n.splice(0,n.length,...e.flat())}function pe(n,e){return n[0]*e[0]+n[1]*e[1]}function ge(n){let e=typeof n=="string"?Number(n):n;return typeof e=="number"&&Number.isFinite(e)?e:void 0}var gt=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),Mt=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function ee(n,e,t){for(let s of n){let o=!1;for(let r=0,i=s.length-2;r<s.length;i=r,r+=2){let u=s[r],l=s[r+1],c=s[i],a=s[i+1];l>t!=a>t&&e<(c-u)*(t-l)/(a-l)+u&&(o=!o)}if(o)return!0}return!1}function Re(n,e,t){for(let s of e){let[o,r,i,u]=ve(s);for(let l of n){let[c,a,f,h]=ve(l);if(!(c>i-t||f<o+t||a>u-t||h<r+t)&&(we(l,s,t)||we(s,l,t)))return!0}}return!1}function we(n,e,t){let s=(o,r)=>ee([e],o,r)&&tt(e,o,r)>t;for(let o=0;o+1<n.length;o+=2)if(s(n[o],n[o+1]))return!0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=[0,1];for(let f=0,h=e.length-2;f<e.length;h=f,f+=2){let m=et(i,u,l,c,e[h],e[h+1],e[f],e[f+1]);m!==void 0&&a.push(m)}if(a.length!==2){a.sort((f,h)=>f-h);for(let f=1;f<a.length;f+=1){let h=(a[f-1]+a[f])/2;if(s(i+(l-i)*h,u+(c-u)*h))return!0}}}return!1}function et(n,e,t,s,o,r,i,u){let l=t-n,c=s-e,a=i-o,f=u-r,h=l*f-c*a;if(h===0)return;let m=((o-n)*f-(r-e)*a)/h,b=((o-n)*c-(r-e)*l)/h;return m>0&&m<1&&b>=0&&b<=1?m:void 0}function tt(n,e,t){let s=1/0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=(l-i)**2+(c-u)**2,f=a>0?Math.max(0,Math.min(1,((e-i)*(l-i)+(t-u)*(c-u))/a)):0;s=Math.min(s,Math.hypot(e-(i+(l-i)*f),t-(u+(c-u)*f)))}return s}function ve(n){let[e,t,s,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<n.length;r+=2)e=Math.min(e,n[r]),s=Math.max(s,n[r]),t=Math.min(t,n[r+1]),o=Math.max(o,n[r+1]);return[e,t,s,o]}var tn=8192,te=28,ot={commercial:1,active:2,destination:3},rt={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(n=>[n,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(n=>[n,3]))},st={dining:1,entertainment:2,nightlife:3},oe={...Object.fromEntries(["alcoholic_beverage_venue","bar","beer","biergarten","brewery","casino","cocktail_bar","dance_club","karaoke","lounge","music_venue","night_club","nightclub","pub","wine_bar"].map(n=>[n,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(n=>[n,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(n=>[n,1]))},ut=6,it=.5,lt=.5;function Ue(n,e){let t=tn/n.extent,s=e.tileMetres/tn,o=e.defaultHeight??5,r=Number.isFinite(e.edgeRadius)?Math.max(0,Math.min(1,e.edgeRadius))/s:0,i=new re,u=new Yn,l=new Yn,c=[],a=[],f=0,h=ht(n,e);for(let[y,k]of n.features.entries()){if(k.type!=="Polygon"||c.length>=65535)continue;let R=k.properties,N=R.hide_3d;if(N===!0||N===1||N==="1"||N==="true")continue;let F=Math.min(6e3,Ae(R.render_height)??o),S=Math.min(F,Ae(R.render_min_height)??0),J=ot[String(R.building_tone??"")]??0,Q=rt[String(R.class??"")]??0,L=h.get(y)??0,C=c.length,$=!1;for(let V of mt(k.geometry,t)){let p=V.map(xt).filter(w=>w.length>=6);if(p.length===0||Xn(p[0])===0)continue;let[A,v]=bt(p[0]);if(e.masks?.length&&(ee(e.masks,A,v)||Re(p,e.masks,lt/s)))continue;let g={cell:Math.min(7,Math.max(0,Math.floor(v/1024)))*8+Math.min(7,Math.max(0,Math.floor(A/1024))),bounds:[1/0,1/0,-1/0,-1/0,F+.1],roofsFrom:u.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let w of p)for(let U=0;U<w.length;U+=2)g.bounds[0]=Math.min(g.bounds[0],w[U]-1),g.bounds[1]=Math.min(g.bounds[1],w[U+1]-1),g.bounds[2]=Math.max(g.bounds[2],w[U]+1),g.bounds[3]=Math.max(g.bounds[3],w[U+1]+1);$=!0,f=Math.max(f,F);let E=Ie(A*.0731+v*.19137+C*1.618),H=p[0].some(w=>w<=0||w>=tn)?Ie(F*7.31+S*3.17+J):E,q={base:S,top:F,jitter:H,seed:E,tone:J,group:Q,use:L,cx:A,cy:v,index:C},on=p.map((w,U)=>ft(w,U,r,e.cornerAngle));p=on.map(({ring:w})=>w);let Y=V.length===1&&p.length===1?Me(V[0],p[0],R,S,F,s,tn):void 0;Y?(f=Math.max(f,Y.ridge),g.bounds[4]=Math.max(g.bounds[4],Y.ridge+.1),at(Y,q,i,u)):ct(p,q,i,u);for(let[w,U]of p.entries()){let rn=Xn(U)>0,T=w===0?rn?-1:1:rn?1:-1,j=U.length/2,z=Z=>{let P=(Z%j+j)%j;return[U[P*2],U[P*2+1]]},nn=Z=>{let[P,D]=z(Z-1),[X,G]=z(Z),[un,mn]=z(Z+1);if(hn(P,D,X,G)||hn(X,G,un,mn))return!1;let bn=Math.hypot(X-P,G-D),ln=Math.hypot(un-X,mn-G);if(!bn||!ln)return!1;let _n=-(G-D)/bn*T,Pn=(X-P)/bn*T;return(_n*(un-X)+Pn*(mn-G))/ln>it},en=(Z,P)=>Y?Y.heightAt(Z,P):F,pn=0;for(let Z=0;Z<j;Z+=1){let[P,D]=z(Z),[X,G]=z(Z+1),un=Math.hypot(X-P,G-D);if(un===0||hn(P,D,X,G))continue;let mn=-(G-D)/un*T,bn=(X-P)/un*T,ln=on[w].normals[Z]??[mn,bn],_n=on[w].normals[(Z+1)%j]??[mn,bn],Pn=W=>{let gn=ln[0]+(_n[0]-ln[0])*W,Mn=ln[1]+(_n[1]-ln[1])*W,cn=Math.hypot(gn,Mn)||1;return[gn/cn,Mn/cn]},sn=[0,...Y?Y.breaks(P,D,X,G):[],1];for(let W=0;W+1<sn.length;W+=1){let gn=W===0,Mn=W+2===sn.length,cn=P+(X-P)*sn[W],On=D+(G-D)*sn[W],Ln=P+(X-P)*sn[W+1],Cn=D+(G-D)*sn[W+1],wn=Math.hypot(Ln-cn,Cn-On)/tn*e.tileMetres,zn=(gn&&nn(Z)?1:0)|(Mn&&nn(Z+1)?2:0)|(w>0?4:0),Zn=(gn&&se(P,D)?1:0)|(Mn&&se(X,G)?2:0),Gn=pn+wn,Sn=Pn(sn[W]),jn=Pn(sn[W+1]),ie=en(cn,On),le=en(Ln,Cn),xn=i.write(q,cn,On,0,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie);i.write(q,Ln,Cn,0,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),i.write(q,cn,On,1,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie),i.write(q,Ln,Cn,1,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),l.push(xn,xn+1,xn+2,xn+1,xn+3,xn+2),pn=Gn}}}g.roofsCount=u.length-g.roofsFrom,g.wallsCount=l.length-g.wallsFrom,a.push(g)}$&&c.push(R)}if(i.count===0)return;let m=new Uint32Array(u.length+l.length),b=[],d=0,x=u.length,I=u.values(),B=l.values();a.sort((y,k)=>y.cell-k.cell);let M=-1;for(let y of a){y.cell!==M&&(M=y.cell,b.push({bounds:[...y.bounds],roofsFrom:d,roofsCount:0,wallsFrom:x,wallsCount:0}));let k=b.at(-1);for(let R=0;R<5;R+=1)k.bounds[R]=R<2?Math.min(k.bounds[R],y.bounds[R]):Math.max(k.bounds[R],y.bounds[R]);m.set(I.subarray(y.roofsFrom,y.roofsFrom+y.roofsCount),d),m.set(B.subarray(y.wallsFrom,y.wallsFrom+y.wallsCount),x),d+=y.roofsCount,x+=y.wallsCount,k.roofsCount+=y.roofsCount,k.wallsCount+=y.wallsCount}return{groups:b,vertices:i.bytes(),indices:m,wallsFrom:u.length,top:f,features:c}}function ct(n,e,t,s){let o=[],r=[];for(let l of n)r.push(o.length/2),o.push(...l);r.push(o.length/2);let i=(l,c)=>{let a=r.findIndex((d,x)=>l>=d&&l<r[x+1]),[f,h]=[r[a],r[a+1]-1];if(c<f||c>h)return!1;let[m,b]=l<c?[l,c]:[c,l];return b-m!==1&&!(m===f&&b===h)?!1:!hn(o[l*2],o[l*2+1],o[c*2],o[c*2+1])},u=kn(o,r.slice(1,-1),2);for(let l=0;l+2<u.length;l+=3){let c=[u[l],u[l+1],u[l+2]],a=0;for(let f=0;f<3;f+=1)i(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries())s.push(t.write(e,o[h*2],o[h*2+1],1,0,0,0,0,0,0,f,a))}}function at(n,e,t,s){for(let o of n.faces){let r=o.ring,i=r.length/2,u=(l,c)=>{let[a,f]=l<c?[l,c]:[c,l];return f-a!==1&&!(a===0&&f===i-1)?!1:!hn(r[l*2],r[l*2+1],r[c*2],r[c*2+1])};for(let l=0;l+2<o.triangles.length;l+=3){let c=[o.triangles[l],o.triangles[l+1],o.triangles[l+2]],a=0;for(let f=0;f<3;f+=1)u(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries()){let m=r[h*2],b=r[h*2+1];s.push(t.write(e,m,b,1,o.normal[0],o.normal[1],0,0,0,0,f,a,n.heightAt(m,b)))}}}}function ft(n,e,t,s=Math.PI/6){if(t<1)return{ring:n,normals:[]};let o=n.length/2,r=Xn(n)>0,i=e===0?r?-1:1:r?1:-1,u=[],l=[],c=(a,f,h)=>{a=yn(a),f=yn(f),!(u.at(-2)===a&&u.at(-1)===f)&&(u.push(a,f),l.push(h))};for(let a=0;a<o;a+=1){let f=(a+o-1)%o,h=(a+1)%o,[m,b]=[n[f*2],n[f*2+1]],[d,x]=[n[a*2],n[a*2+1]],[I,B]=[n[h*2],n[h*2+1]],M=Math.hypot(d-m,x-b),y=Math.hypot(I-d,B-x);if(!M||!y||se(d,x)||hn(m,b,d,x)||hn(d,x,I,B)){c(d,x);continue}let[k,R]=[(d-m)/M,(x-b)/M],[N,F]=[(I-d)/y,(B-x)/y],S=Math.atan2(k*F-R*N,k*N+R*F);if(S*i>=-.08||Math.abs(S)>Math.PI*.85){c(d,x);continue}let J=Math.tan(Math.abs(S)/2),Q=Math.min(t*J,M*.2,y*.2),L=Q/J;if(L<1){c(d,x);continue}let[C,$]=[-R*i,k*i],[V,p]=[d-k*Q-C*L,x-R*Q-$*L],A=Math.atan2($,C),v=Math.max(s<=Math.PI/6?2:1,Math.ceil(Math.abs(S)/Math.max(Math.PI/6,Math.min(Math.PI/3,s))));for(let g=0;g<=v;g+=1){let E=A+S*g/v,H=[Math.cos(E),Math.sin(E)];c(V+H[0]*L,p+H[1]*L,H)}}return u[0]===u.at(-2)&&u[1]===u.at(-1)&&(u.splice(-2),l.pop()),u.length>=6?{ring:u,normals:l}:{ring:n,normals:[]}}var re=class{#n=new ArrayBuffer(4096*te);#e=new DataView(this.#n);count=0;write(e,t,s,o,r,i,u,l,c,a,f=0,h=0,m=e.top){let b=te;if((this.count+1)*b>this.#n.byteLength){let I=new ArrayBuffer(this.#n.byteLength*2);new Uint8Array(I).set(new Uint8Array(this.#n)),this.#n=I,this.#e=new DataView(I)}let d=this.#e,x=this.count*b;return d.setUint16(x,yn(t),!0),d.setUint16(x+2,yn(s),!0),d.setUint16(x+4,Math.min(65535,Math.round(e.base*10)),!0),d.setUint16(x+6,Math.min(65535,Math.max(0,Math.round(m*10))),!0),d.setInt8(x+8,Math.round(r*127)),d.setInt8(x+9,Math.round(i*127)),d.setUint8(x+10,o),d.setUint8(x+11,u),d.setUint16(x+12,Math.round(l*10)%65536,!0),d.setUint8(x+14,Math.round(e.jitter*255)),d.setUint8(x+15,Math.round(e.seed*255)),d.setUint16(x+16,yn(e.cx),!0),d.setUint16(x+18,yn(e.cy),!0),d.setUint8(x+20,e.tone+4*e.group),d.setUint8(x+21,e.use),d.setUint8(x+22,c),d.setUint8(x+23,Math.min(255,Math.round(a))),d.setUint16(x+24,e.index,!0),d.setUint8(x+26,f),d.setUint8(x+27,h),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#n.slice(0,this.count*te))}},Yn=class{#n=new Uint32Array(8192);length=0;push(...e){if(this.length+e.length>this.#n.length){let t=new Uint32Array(Math.max(this.length+e.length,this.#n.length*2));t.set(this.#n),this.#n=t}for(let t of e)this.#n[this.length++]=t}values(){return this.#n.subarray(0,this.length)}};function ht(n,e){let t=new Map;if(n.features.some(u=>u.properties.night_use!==void 0)){for(let[u,l]of n.features.entries()){let c=st[String(l.properties.night_use??"")];c&&t.set(u,c)}return t}let s=e.places;if(!s||s.features.length===0)return t;let o=ut/e.tileMetres*n.extent,r=[];for(let[u,l]of n.features.entries()){if(l.type!=="Polygon")continue;let c=l.properties.hide_3d;if(c===!0||c===1||c==="1"||c==="true")continue;let a=[1/0,1/0,-1/0,-1/0];for(let f of l.geometry)for(let h=0;h+1<f.length;h+=2)a[0]=Math.min(a[0],f[h]),a[1]=Math.min(a[1],f[h+1]),a[2]=Math.max(a[2],f[h]),a[3]=Math.max(a[3],f[h+1]);r.push({index:u,rings:l.geometry,box:a})}let i=n.extent/s.extent;for(let u of s.features){if(u.type!=="Point")continue;let l=u.properties,c=oe[String(l.type??"")]??oe[String(l.subclass??"")]??oe[String(l.class??"")];if(c)for(let a of u.geometry)for(let f=0;f+1<a.length;f+=2){let h=a[f]*i,m=a[f+1]*i,b,d,x=o;for(let B of r){let[M,y,k,R]=B.box;if(h<M-o||h>k+o||m<y-o||m>R+o)continue;let N=!1;for(let F of B.rings)for(let S=0,J=F.length-2;S+1<F.length;J=S,S+=2){let Q=F[J],L=F[J+1],C=F[S],$=F[S+1];$>m!=L>m&&h<(Q-C)*(m-$)/(L-$)+C&&(N=!N);let V=C-Q,p=$-L,A=V*V+p*p,v=A?Math.min(1,Math.max(0,((h-Q)*V+(m-L)*p)/A)):0,g=Math.hypot(h-Q-v*V,m-L-v*p);g<x&&(x=g,d=B.index)}if(N){b=B.index;break}}let I=b??d;I!==void 0&&t.set(I,Math.max(t.get(I)??0,c))}}return t}function yn(n){return Math.min(tn,Math.max(0,Math.round(n)))}function Ae(n){let e=Number(n);return Number.isFinite(e)&&e>0?e:void 0}function mt(n,e){let t=[],s;for(let o of n){let r=[];for(let u=0;u+1<o.length;u+=2){let l=o[u]*e,c=o[u+1]*e;r.length&&l===r.at(-2)&&c===r.at(-1)||r.push(l,c)}if(r.length>=6&&r[0]===r.at(-2)&&r[1]===r.at(-1)&&(r.length-=2),r.length<6)continue;let i=Math.sign(Xn(r));i&&(s??=i,i===s||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function Xn(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e}function bt(n){let e=0,t=0,s=n.length/2;for(let o=0;o<s;o+=1)e+=n[o*2],t+=n[o*2+1];return[e/s,t/s]}function se(n,e){return n<=0||e<=0||n>=tn||e>=tn}function hn(n,e,t,s){let o=tn;return n<=0&&t<=0||n>=o&&t>=o||e<=0&&s<=0||e>=o&&s>=o}function xt(n){let e=tn,t=[...n],s=[[0,0,o=>o>=0],[0,e,o=>o<=e],[1,0,o=>o>=0],[1,e,o=>o<=e]];for(let[o,r,i]of s){let u=[],l=t.length/2;for(let c=0;c<l;c+=1){let a=[t[c*2],t[c*2+1]],f=[t[(c+1)%l*2],t[(c+1)%l*2+1]],h=i(a[o]),m=i(f[o]);if(h&&u.push(a[0],a[1]),h!==m){let b=(r-a[o])/(f[o]-a[o]);u.push(a[0]+(f[0]-a[0])*b,a[1]+(f[1]-a[1])*b)}}if(t=u,t.length<6)return[]}return t}function Ie(n){let e=Math.sin(n*12.9898+78.233)*43758.5453;return e-Math.floor(e)}var ue=globalThis;ue.onmessage=({data:n})=>{try{let e=Ue(n.layer,n.options);ue.postMessage({mesh:e},e?[e.vertices.buffer,e.indices.buffer]:[])}catch(e){ue.postMessage({error:String(e)})}};})();\n'], { type: "text/javascript" })
|
|
6582
|
+
);
|
|
6583
|
+
try {
|
|
6584
|
+
return new Worker(url, { name: "tileflow-buildings" });
|
|
6585
|
+
} finally {
|
|
6586
|
+
URL.revokeObjectURL(url);
|
|
6587
|
+
}
|
|
6588
|
+
}
|
|
6589
|
+
function createBuildingMesher(createWorker = browserWorker) {
|
|
6590
|
+
let worker2;
|
|
6591
|
+
let unavailable = false;
|
|
6592
|
+
let disposed = false;
|
|
6593
|
+
let finish;
|
|
6594
|
+
let tail = Promise.resolve();
|
|
6595
|
+
return {
|
|
6596
|
+
build(layer, options, signal) {
|
|
6597
|
+
const result = tail.then(async () => {
|
|
6598
|
+
if (disposed || signal.aborted) throw new Error("Building mesh cancelled");
|
|
6599
|
+
if (!worker2 && !unavailable) {
|
|
6600
|
+
try {
|
|
6601
|
+
worker2 = createWorker();
|
|
6602
|
+
} catch {
|
|
6603
|
+
}
|
|
6604
|
+
if (!worker2) unavailable = true;
|
|
6605
|
+
}
|
|
6606
|
+
if (!worker2) return meshTileflowBuildings(layer, options);
|
|
6607
|
+
const active = worker2;
|
|
6608
|
+
return new Promise((resolve, reject) => {
|
|
6609
|
+
const cleanup = () => {
|
|
6610
|
+
clearTimeout(timeout);
|
|
6611
|
+
finish = void 0;
|
|
6612
|
+
active.onmessage = null;
|
|
6613
|
+
active.onerror = null;
|
|
6614
|
+
};
|
|
6615
|
+
const fallback = () => {
|
|
6616
|
+
cleanup();
|
|
6617
|
+
active.terminate();
|
|
6618
|
+
worker2 = void 0;
|
|
6619
|
+
unavailable = true;
|
|
6620
|
+
if (disposed || signal.aborted) {
|
|
6621
|
+
reject(new Error("Building mesh cancelled"));
|
|
6622
|
+
return;
|
|
6623
|
+
}
|
|
6624
|
+
try {
|
|
6625
|
+
resolve(meshTileflowBuildings(layer, options));
|
|
6626
|
+
} catch (error) {
|
|
6627
|
+
reject(error);
|
|
6628
|
+
}
|
|
6629
|
+
};
|
|
6630
|
+
const timeout = setTimeout(fallback, 15e3);
|
|
6631
|
+
finish = () => {
|
|
6632
|
+
cleanup();
|
|
6633
|
+
reject(new Error("Building mesher disposed"));
|
|
6634
|
+
};
|
|
6635
|
+
active.onerror = (event) => {
|
|
6636
|
+
event.preventDefault();
|
|
6637
|
+
fallback();
|
|
6638
|
+
};
|
|
6639
|
+
active.onmessage = ({
|
|
6640
|
+
data
|
|
6641
|
+
}) => {
|
|
6642
|
+
cleanup();
|
|
6643
|
+
if (disposed || signal.aborted) reject(new Error("Building mesh cancelled"));
|
|
6644
|
+
else if (data.error) reject(new Error(data.error));
|
|
6645
|
+
else resolve(data.mesh);
|
|
6646
|
+
};
|
|
6647
|
+
try {
|
|
6648
|
+
active.postMessage({ layer, options });
|
|
6649
|
+
} catch {
|
|
6650
|
+
fallback();
|
|
6651
|
+
}
|
|
6652
|
+
});
|
|
6653
|
+
});
|
|
6654
|
+
tail = result.then(
|
|
6655
|
+
() => {
|
|
6656
|
+
},
|
|
6657
|
+
() => {
|
|
6658
|
+
}
|
|
6659
|
+
);
|
|
6660
|
+
return result;
|
|
6661
|
+
},
|
|
6662
|
+
dispose() {
|
|
6663
|
+
disposed = true;
|
|
6664
|
+
finish?.();
|
|
6665
|
+
worker2?.terminate();
|
|
6666
|
+
worker2 = void 0;
|
|
6667
|
+
}
|
|
6668
|
+
};
|
|
6669
|
+
}
|
|
6670
|
+
|
|
6671
|
+
// src/buildings-3d-occlusion.ts
|
|
6672
|
+
var tileflowBuildingOcclusion = {
|
|
6673
|
+
ground: 3,
|
|
6674
|
+
groundStrength: 0.16,
|
|
6675
|
+
wall: 0.14,
|
|
6676
|
+
wallRadius: 3,
|
|
6677
|
+
corner: 3
|
|
6678
|
+
};
|
|
6679
|
+
function buildingContactSegments(mesh) {
|
|
6680
|
+
const count = (mesh.indices.length - mesh.wallsFrom) / 6;
|
|
6681
|
+
const bytes = new Uint8Array(count * 16);
|
|
6682
|
+
for (let wall = 0; wall < count; wall += 1) {
|
|
6683
|
+
const index = mesh.wallsFrom + wall * 6;
|
|
6684
|
+
const start = mesh.indices[index] * tileflowBuildingVertexBytes;
|
|
6685
|
+
const end = mesh.indices[index + 1] * tileflowBuildingVertexBytes;
|
|
6686
|
+
bytes.set(mesh.vertices.subarray(start, start + 4), wall * 16);
|
|
6687
|
+
bytes.set(mesh.vertices.subarray(end, end + 4), wall * 16 + 4);
|
|
6688
|
+
bytes.set(mesh.vertices.subarray(start + 4, start + 8), wall * 16 + 8);
|
|
6689
|
+
bytes[wall * 16 + 12] = mesh.vertices[start + 14];
|
|
6690
|
+
bytes[wall * 16 + 13] = mesh.vertices[start + 27];
|
|
6691
|
+
}
|
|
6692
|
+
return bytes;
|
|
6693
|
+
}
|
|
6694
|
+
function createBuildingContactVao(gl, mesh) {
|
|
6695
|
+
const vao = gl.createVertexArray();
|
|
6696
|
+
const vbo = gl.createBuffer();
|
|
6697
|
+
gl.bindVertexArray(vao);
|
|
6698
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
6699
|
+
const segments = buildingContactSegments(mesh);
|
|
6700
|
+
gl.bufferData(gl.ARRAY_BUFFER, segments, gl.STATIC_DRAW);
|
|
6701
|
+
const offset = (first) => {
|
|
6702
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
|
|
6703
|
+
for (const [location, size, type, normalised, byte2] of [
|
|
6704
|
+
[0, 2, gl.UNSIGNED_SHORT, false, 0],
|
|
6705
|
+
[1, 2, gl.UNSIGNED_SHORT, false, 4],
|
|
6706
|
+
[2, 2, gl.UNSIGNED_SHORT, false, 8],
|
|
6707
|
+
[3, 1, gl.UNSIGNED_BYTE, true, 12],
|
|
6708
|
+
[4, 1, gl.UNSIGNED_BYTE, false, 13]
|
|
6709
|
+
]) {
|
|
6710
|
+
gl.enableVertexAttribArray(location);
|
|
6711
|
+
gl.vertexAttribPointer(location, size, type, normalised, 16, first * 16 + byte2);
|
|
6712
|
+
gl.vertexAttribDivisor(location, 1);
|
|
6713
|
+
}
|
|
6714
|
+
};
|
|
6715
|
+
offset(0);
|
|
6716
|
+
gl.bindVertexArray(null);
|
|
6717
|
+
return { vao, vbo, count: segments.length / 16, offset };
|
|
6718
|
+
}
|
|
6719
|
+
var buildingContactVertex = (projected) => `#version 300 es
|
|
6720
|
+
${projected}
|
|
6721
|
+
layout(location=0) in vec2 a_start;
|
|
6722
|
+
layout(location=1) in vec2 a_end;
|
|
6723
|
+
layout(location=2) in vec2 a_heights;
|
|
6724
|
+
layout(location=3) in float a_seed;
|
|
6725
|
+
layout(location=4) in float a_cuts;
|
|
6726
|
+
uniform float u_scale;
|
|
6727
|
+
uniform float u_metres;
|
|
6728
|
+
uniform float u_band;
|
|
6729
|
+
out vec2 v_point;
|
|
6730
|
+
flat out float v_length;
|
|
6731
|
+
flat out float v_strength;
|
|
6732
|
+
void main() {
|
|
6733
|
+
float p = clamp(u_scale * 1.25 - a_seed * 0.25, 0.0, 1.0);
|
|
6734
|
+
if (p <= 0.0 || a_heights.x > 5.0 || a_heights.y <= a_heights.x) {
|
|
6735
|
+
gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
|
|
6736
|
+
return;
|
|
6737
|
+
}
|
|
6738
|
+
vec2 edge = (a_end - a_start) * u_metres;
|
|
6739
|
+
v_length = length(edge);
|
|
6740
|
+
vec2 tangent = edge / max(v_length, 0.0001);
|
|
6741
|
+
// A tile cut continues in its neighbour; do not round that artificial endpoint.
|
|
6742
|
+
float startCap = mod(a_cuts, 2.0) < 0.5 ? u_band : 0.0;
|
|
6743
|
+
float endCap = a_cuts < 2.0 ? u_band : 0.0;
|
|
6744
|
+
v_point = vec2((gl_VertexID & 1) == 0 ? -startCap : v_length + endCap,
|
|
6745
|
+
gl_VertexID < 2 ? -u_band : u_band);
|
|
6746
|
+
vec2 point = a_start + (tangent * v_point.x + vec2(-tangent.y, tangent.x) * v_point.y) / u_metres;
|
|
6747
|
+
gl_Position = projectTileFor3D(point, 0.0);
|
|
6748
|
+
v_strength = p * p * (3.0 - 2.0 * p);
|
|
6749
|
+
}`;
|
|
6750
|
+
var buildingContactFragment = `#version 300 es
|
|
6751
|
+
precision highp float;
|
|
6752
|
+
in vec2 v_point;
|
|
6753
|
+
flat in float v_length;
|
|
6754
|
+
flat in float v_strength;
|
|
6755
|
+
uniform float u_band;
|
|
6756
|
+
uniform float u_band_strength;
|
|
6757
|
+
out vec4 fragColor;
|
|
6758
|
+
void main() {
|
|
6759
|
+
float distance = length(v_point - vec2(clamp(v_point.x, 0.0, v_length), 0.0));
|
|
6760
|
+
float near = 1.0 - smoothstep(0.0, u_band, distance);
|
|
6761
|
+
float shade = 1.0 - u_band_strength * near * near * v_strength;
|
|
6762
|
+
fragColor = vec4(vec3(shade), 1.0);
|
|
6763
|
+
}`;
|
|
6764
|
+
var buildingContactCompositeFragment = `#version 300 es
|
|
6765
|
+
precision highp float;
|
|
6766
|
+
uniform highp sampler2D u_contact;
|
|
6767
|
+
out vec4 fragColor;
|
|
6768
|
+
void main() {
|
|
6769
|
+
float shade = texelFetch(u_contact, ivec2(gl_FragCoord.xy), 0).r;
|
|
6770
|
+
fragColor = vec4(vec3(shade), 1.0);
|
|
6771
|
+
}`;
|
|
6772
|
+
function createBuildingContactMask() {
|
|
6773
|
+
let target;
|
|
6774
|
+
function release(gl) {
|
|
6775
|
+
if (!target) return;
|
|
6776
|
+
gl.deleteTexture(target.texture);
|
|
6777
|
+
gl.deleteFramebuffer(target.framebuffer);
|
|
6778
|
+
target = void 0;
|
|
6779
|
+
}
|
|
6780
|
+
return {
|
|
6781
|
+
release,
|
|
6782
|
+
render(gl, draw) {
|
|
6783
|
+
const framebuffer = gl.getParameter(gl.DRAW_FRAMEBUFFER_BINDING);
|
|
6784
|
+
const depth = gl.isEnabled(gl.DEPTH_TEST);
|
|
6785
|
+
const stencil = gl.isEnabled(gl.STENCIL_TEST);
|
|
6786
|
+
const cull = gl.isEnabled(gl.CULL_FACE);
|
|
6787
|
+
const scissor = gl.isEnabled(gl.SCISSOR_TEST);
|
|
6788
|
+
const equation = gl.getParameter(gl.BLEND_EQUATION_RGB);
|
|
6789
|
+
const alphaEquation = gl.getParameter(gl.BLEND_EQUATION_ALPHA);
|
|
6790
|
+
const clear = gl.getParameter(gl.COLOR_CLEAR_VALUE);
|
|
6791
|
+
try {
|
|
6792
|
+
const width = gl.drawingBufferWidth;
|
|
6793
|
+
const height = gl.drawingBufferHeight;
|
|
6794
|
+
if (!target || target.width !== width || target.height !== height) {
|
|
6795
|
+
release(gl);
|
|
6796
|
+
const texture = gl.createTexture();
|
|
6797
|
+
gl.activeTexture(gl.TEXTURE2);
|
|
6798
|
+
gl.bindTexture(gl.TEXTURE_2D, texture);
|
|
6799
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.R8, width, height);
|
|
6800
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
6801
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
6802
|
+
const next = gl.createFramebuffer();
|
|
6803
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, next);
|
|
6804
|
+
gl.framebufferTexture2D(
|
|
6805
|
+
gl.DRAW_FRAMEBUFFER,
|
|
6806
|
+
gl.COLOR_ATTACHMENT0,
|
|
6807
|
+
gl.TEXTURE_2D,
|
|
6808
|
+
texture,
|
|
6809
|
+
0
|
|
6810
|
+
);
|
|
6811
|
+
target = { texture, framebuffer: next, width, height };
|
|
6812
|
+
}
|
|
6813
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, target.framebuffer);
|
|
6814
|
+
gl.disable(gl.DEPTH_TEST);
|
|
6815
|
+
gl.disable(gl.STENCIL_TEST);
|
|
6816
|
+
gl.disable(gl.CULL_FACE);
|
|
6817
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
6818
|
+
gl.clearColor(1, 1, 1, 1);
|
|
6819
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
6820
|
+
gl.blendEquation(gl.MIN);
|
|
6821
|
+
draw();
|
|
6822
|
+
return target.texture;
|
|
6823
|
+
} finally {
|
|
6824
|
+
gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, framebuffer);
|
|
6825
|
+
gl.blendEquationSeparate(equation, alphaEquation);
|
|
6826
|
+
gl.clearColor(clear[0], clear[1], clear[2], clear[3]);
|
|
6827
|
+
for (const [capability, enabled] of [
|
|
6828
|
+
[gl.DEPTH_TEST, depth],
|
|
6829
|
+
[gl.STENCIL_TEST, stencil],
|
|
6830
|
+
[gl.CULL_FACE, cull],
|
|
6831
|
+
[gl.SCISSOR_TEST, scissor]
|
|
6832
|
+
]) {
|
|
6833
|
+
if (enabled) gl.enable(capability);
|
|
6834
|
+
else gl.disable(capability);
|
|
6835
|
+
}
|
|
6836
|
+
}
|
|
6837
|
+
}
|
|
6838
|
+
};
|
|
6839
|
+
}
|
|
6840
|
+
|
|
6841
|
+
// src/buildings-3d-opacity.ts
|
|
6842
|
+
function drawBuildingOpacity(gl, opacity, draw) {
|
|
6843
|
+
if (opacity <= 0) return;
|
|
6844
|
+
if (opacity >= 1) {
|
|
6845
|
+
draw();
|
|
6846
|
+
return;
|
|
6847
|
+
}
|
|
6848
|
+
const colourMask = gl.getParameter(gl.COLOR_WRITEMASK);
|
|
6849
|
+
const depthMask = gl.getParameter(gl.DEPTH_WRITEMASK);
|
|
6850
|
+
const depthFunc = gl.getParameter(gl.DEPTH_FUNC);
|
|
6851
|
+
const blending = gl.isEnabled(gl.BLEND);
|
|
6852
|
+
const blendColour = gl.getParameter(gl.BLEND_COLOR);
|
|
6853
|
+
const blend = [gl.BLEND_SRC_RGB, gl.BLEND_DST_RGB, gl.BLEND_SRC_ALPHA, gl.BLEND_DST_ALPHA].map(
|
|
6854
|
+
(name) => gl.getParameter(name)
|
|
6855
|
+
);
|
|
6856
|
+
const equations = [gl.BLEND_EQUATION_RGB, gl.BLEND_EQUATION_ALPHA].map(
|
|
6857
|
+
(name) => gl.getParameter(name)
|
|
6858
|
+
);
|
|
6859
|
+
try {
|
|
6860
|
+
gl.colorMask(false, false, false, false);
|
|
6861
|
+
gl.depthMask(true);
|
|
6862
|
+
gl.depthFunc(gl.LEQUAL);
|
|
6863
|
+
gl.disable(gl.BLEND);
|
|
6864
|
+
draw();
|
|
6865
|
+
gl.colorMask(...colourMask);
|
|
6866
|
+
gl.depthMask(false);
|
|
6867
|
+
gl.depthFunc(gl.EQUAL);
|
|
6868
|
+
gl.enable(gl.BLEND);
|
|
6869
|
+
gl.blendEquationSeparate(gl.FUNC_ADD, gl.FUNC_ADD);
|
|
6870
|
+
gl.blendColor(0, 0, 0, opacity);
|
|
6871
|
+
gl.blendFuncSeparate(gl.CONSTANT_ALPHA, gl.ONE_MINUS_CONSTANT_ALPHA, gl.ZERO, gl.ONE);
|
|
6872
|
+
draw();
|
|
6873
|
+
} finally {
|
|
6874
|
+
gl.colorMask(...colourMask);
|
|
6875
|
+
gl.depthMask(depthMask);
|
|
6876
|
+
gl.depthFunc(depthFunc);
|
|
6877
|
+
gl.blendColor(...Array.from(blendColour));
|
|
6878
|
+
gl.blendFuncSeparate(blend[0], blend[1], blend[2], blend[3]);
|
|
6879
|
+
gl.blendEquationSeparate(equations[0], equations[1]);
|
|
6880
|
+
if (blending) gl.enable(gl.BLEND);
|
|
6881
|
+
else gl.disable(gl.BLEND);
|
|
6882
|
+
}
|
|
6883
|
+
}
|
|
6884
|
+
|
|
6885
|
+
// src/scene-3d-shaders.ts
|
|
6886
|
+
var tileflowShadowLookup = `
|
|
6887
|
+
uniform highp sampler2DShadow u_shadow_map;
|
|
6888
|
+
uniform float u_shadow;
|
|
6889
|
+
uniform float u_depth_unit;
|
|
6890
|
+
|
|
6891
|
+
float visibility(vec3 c, float bias) {
|
|
6892
|
+
if (u_shadow <= 0.0 || c.x <= 0.0 || c.y <= 0.0 || c.x >= 1.0 || c.y >= 1.0 || c.z >= 1.0)
|
|
6893
|
+
return 1.0;
|
|
6894
|
+
float z = c.z - bias * u_depth_unit;
|
|
6895
|
+
// The depth texture uses LINEAR comparison; an extra multi-tap kernel would
|
|
6896
|
+
// spread distant texels into metres of soft shadow.
|
|
6897
|
+
return texture(u_shadow_map, vec3(c.xy, z));
|
|
6898
|
+
}`;
|
|
6899
|
+
|
|
6296
6900
|
// src/buildings-3d-shaders.ts
|
|
6297
6901
|
var tileflowBuildingAttributes = [
|
|
6298
6902
|
["a_pos", 2, "UNSIGNED_SHORT", false, 0],
|
|
@@ -6365,24 +6969,6 @@ vec3 shopOf(float use, float seed) {
|
|
|
6365
6969
|
return vec3(0.0);
|
|
6366
6970
|
}
|
|
6367
6971
|
`;
|
|
6368
|
-
var shadowLookup = `
|
|
6369
|
-
uniform highp sampler2DShadow u_shadow_map;
|
|
6370
|
-
uniform vec2 u_shadow_texel;
|
|
6371
|
-
uniform float u_shadow; // how dark shadows fall, 0 for none
|
|
6372
|
-
uniform float u_depth_unit; // the depth of a metre
|
|
6373
|
-
|
|
6374
|
-
float visibility(vec3 c, float bias) {
|
|
6375
|
-
if (u_shadow <= 0.0 || c.x <= 0.0 || c.y <= 0.0 || c.x >= 1.0 || c.y >= 1.0 || c.z >= 1.0)
|
|
6376
|
-
return 1.0;
|
|
6377
|
-
float z = c.z - bias * u_depth_unit;
|
|
6378
|
-
vec2 o = u_shadow_texel * 0.75;
|
|
6379
|
-
return 0.25 * (
|
|
6380
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, -o.y), z)) +
|
|
6381
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, -o.y), z)) +
|
|
6382
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, o.y), z)) +
|
|
6383
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, o.y), z)));
|
|
6384
|
-
}
|
|
6385
|
-
`;
|
|
6386
6972
|
var tileflowBuildingDepthVertex = `#version 300 es
|
|
6387
6973
|
${attributes}
|
|
6388
6974
|
uniform mat4 u_light_matrix;
|
|
@@ -6415,12 +7001,8 @@ uniform vec2 u_palette_themes; // the themes either side of the blend position
|
|
|
6415
7001
|
uniform float u_palette_mix;
|
|
6416
7002
|
uniform float u_variation; // how far buildings' brightness strays from their colour, either way
|
|
6417
7003
|
${growth}
|
|
6418
|
-
uniform vec3 u_style_light_position;
|
|
6419
|
-
uniform vec3 u_style_light_colour;
|
|
6420
|
-
uniform float u_style_light_intensity;
|
|
6421
|
-
uniform float u_style_gradient;
|
|
6422
|
-
out vec3 v_style_colour;
|
|
6423
7004
|
out vec3 v_colour;
|
|
7005
|
+
out float v_base;
|
|
6424
7006
|
out vec3 v_facade; // metres along the wall, metres up as built, the building's top
|
|
6425
7007
|
out vec4 v_building; // seed, tone and class group, wall, night use
|
|
6426
7008
|
out vec3 v_normal;
|
|
@@ -6435,22 +7017,6 @@ vec3 paletteColour(float theme) {
|
|
|
6435
7017
|
return texelFetch(u_palette, ivec2(int(column), int(row)), 0).rgb;
|
|
6436
7018
|
}
|
|
6437
7019
|
|
|
6438
|
-
// MapLibre GL JS 6.13 fill-extrusion lighting, adapted to this mesh's normals and heights.
|
|
6439
|
-
// Copyright MapLibre contributors and Mapbox; BSD-3-Clause, see THIRD_PARTY_NOTICES.md.
|
|
6440
|
-
vec3 styleColour(vec3 colour, vec3 normal, float base, float top) {
|
|
6441
|
-
float luminance = dot(colour, vec3(0.2126, 0.7152, 0.0722));
|
|
6442
|
-
float facing = clamp(dot(normal * vec3(1.0, -1.0, 1.0), u_style_light_position), 0.0, 1.0);
|
|
6443
|
-
float brightness = mix(1.0 - u_style_light_intensity,
|
|
6444
|
-
max(1.0 - luminance + u_style_light_intensity, 1.0), facing);
|
|
6445
|
-
if (a_flags.y > 0.5) {
|
|
6446
|
-
float gradient = clamp((a_flags.x + base) * sqrt(top / 150.0),
|
|
6447
|
-
mix(0.7, 0.98, 1.0 - u_style_light_intensity), 1.0);
|
|
6448
|
-
brightness *= mix(1.0, gradient, u_style_gradient);
|
|
6449
|
-
}
|
|
6450
|
-
return clamp((colour + 0.03) * brightness * u_style_light_colour,
|
|
6451
|
-
0.3 * (1.0 - u_style_light_colour), vec3(1.0));
|
|
6452
|
-
}
|
|
6453
|
-
|
|
6454
7020
|
void main() {
|
|
6455
7021
|
float base = a_height.x * 0.1;
|
|
6456
7022
|
float top = a_height.y * 0.1;
|
|
@@ -6476,7 +7042,7 @@ void main() {
|
|
|
6476
7042
|
: u_colour;
|
|
6477
7043
|
// Neighbours of one colour stay apart: each building a little lighter or darker than its colour.
|
|
6478
7044
|
v_colour *= 1.0 + (a_wave.x - 0.5) * 2.0 * u_variation;
|
|
6479
|
-
|
|
7045
|
+
v_base = base;
|
|
6480
7046
|
v_edge = wall > 0.5 ? vec3(a_edge.x, 0.0, 0.0) : vec3(equal(vec3(a_edge.x), vec3(0.0, 1.0, 2.0)));
|
|
6481
7047
|
v_outline = a_edge.y;
|
|
6482
7048
|
v_facade = vec3(a_along * 0.1, own, top);
|
|
@@ -6486,7 +7052,12 @@ void main() {
|
|
|
6486
7052
|
var tileflowBuildingFragment = `#version 300 es
|
|
6487
7053
|
precision highp float;
|
|
6488
7054
|
in vec3 v_colour;
|
|
6489
|
-
in
|
|
7055
|
+
in float v_base;
|
|
7056
|
+
uniform vec3 u_style_light_position;
|
|
7057
|
+
uniform vec3 u_style_light_colour;
|
|
7058
|
+
uniform float u_style_light_intensity;
|
|
7059
|
+
uniform float u_style_gradient;
|
|
7060
|
+
uniform float u_edge_radius;
|
|
6490
7061
|
uniform float u_sun_lighting;
|
|
6491
7062
|
in vec3 v_facade;
|
|
6492
7063
|
in vec4 v_building;
|
|
@@ -6499,6 +7070,7 @@ uniform vec3 u_sun_dir;
|
|
|
6499
7070
|
uniform vec3 u_sun_light;
|
|
6500
7071
|
uniform vec3 u_sky_light;
|
|
6501
7072
|
uniform float u_occlusion;
|
|
7073
|
+
uniform float u_occlusion_radius;
|
|
6502
7074
|
uniform float u_corner;
|
|
6503
7075
|
uniform float u_night; // 0 by day, 1 at night
|
|
6504
7076
|
uniform float u_windows; // whether to draw night windows
|
|
@@ -6508,13 +7080,48 @@ uniform float u_edge_width; // their width, in the drawing buffer's pixels
|
|
|
6508
7080
|
uniform vec3 u_edge_colour; // the application's colour for them
|
|
6509
7081
|
uniform float u_edge_own; // how much of them takes the application's colour, from 0 to 1
|
|
6510
7082
|
${nightLight}
|
|
6511
|
-
${
|
|
7083
|
+
${tileflowShadowLookup}
|
|
6512
7084
|
out vec4 fragColor;
|
|
6513
7085
|
|
|
6514
7086
|
float hash(vec2 p) {
|
|
6515
7087
|
return fract(sin(dot(p, vec2(127.1, 311.7))) * 43758.5453);
|
|
6516
7088
|
}
|
|
6517
7089
|
|
|
7090
|
+
// The contact shade rises gradually up the facade, in world metres. Use one bounded shade
|
|
7091
|
+
// for the authored gradient, ground contact and inner corners, rather than multiplying them.
|
|
7092
|
+
float facadeShade() {
|
|
7093
|
+
if (v_building.z < 0.5) return 1.0;
|
|
7094
|
+
float reach = min(3.0, max((v_facade.z - v_base) * 0.5, 0.1));
|
|
7095
|
+
float gradient = 0.1 * u_style_gradient * (1.0 - smoothstep(0.0, reach, v_facade.y - v_base));
|
|
7096
|
+
// Exponential attenuation leaves a soft tail above the first floor, without a band ending
|
|
7097
|
+
// at its radius. Height above ground also keeps raised parts from acquiring a dark lower rim.
|
|
7098
|
+
float up = max(v_facade.y, 0.0);
|
|
7099
|
+
float contact = u_occlusion * exp(-up / max(u_occlusion_radius, 0.001));
|
|
7100
|
+
float along = v_facade.x - v_wall.x;
|
|
7101
|
+
float corner = 0.0;
|
|
7102
|
+
if (mod(v_wall.y, 2.0) >= 1.0) corner = 1.0 - smoothstep(0.0, u_corner, along);
|
|
7103
|
+
if (mod(floor(v_wall.y / 2.0), 2.0) >= 1.0)
|
|
7104
|
+
corner = max(corner, 1.0 - smoothstep(0.0, u_corner, v_wall.z - along));
|
|
7105
|
+
float inner = u_occlusion * 0.7 * corner * mix(1.0, 0.5, clamp(up / 30.0, 0.0, 1.0));
|
|
7106
|
+
return 1.0 - max(gradient, max(contact, inner));
|
|
7107
|
+
}
|
|
7108
|
+
|
|
7109
|
+
// MapLibre GL JS 6.13 fill-extrusion light, evaluated per fragment for curved corner normals.
|
|
7110
|
+
// Copyright MapLibre contributors and Mapbox; BSD-3-Clause, see THIRD_PARTY_NOTICES.md.
|
|
7111
|
+
vec3 styleColour(vec3 colour, vec3 normal) {
|
|
7112
|
+
float luminance = dot(colour, vec3(0.2126, 0.7152, 0.0722));
|
|
7113
|
+
float facing = clamp(dot(normal * vec3(1.0, -1.0, 1.0), u_style_light_position), 0.0, 1.0);
|
|
7114
|
+
float brightness = mix(1.0 - u_style_light_intensity,
|
|
7115
|
+
max(1.0 - luminance + u_style_light_intensity, 1.0), facing);
|
|
7116
|
+
if (v_building.z > 0.5) {
|
|
7117
|
+
float wallLight = clamp((1.0 + v_base) * sqrt(v_facade.z / 150.0),
|
|
7118
|
+
mix(0.7, 0.98, 1.0 - u_style_light_intensity), 1.0);
|
|
7119
|
+
brightness *= mix(1.0, mix(wallLight, 1.0, normal.z), u_style_gradient);
|
|
7120
|
+
}
|
|
7121
|
+
return clamp((colour + 0.03) * brightness * u_style_light_colour,
|
|
7122
|
+
0.3 * (1.0 - u_style_light_colour), vec3(1.0));
|
|
7123
|
+
}
|
|
7124
|
+
|
|
6518
7125
|
// A surface's light over the light a sunlit roof takes, in the map's colours: a sunlit roof keeps
|
|
6519
7126
|
// its colour, a wall turned to a low sun glows, and shade takes the sky's colour. A wall sees half
|
|
6520
7127
|
// the sky; by day the ground's light makes up some of the rest, at night little does.
|
|
@@ -6548,9 +7155,17 @@ float edgeOf() {
|
|
|
6548
7155
|
|
|
6549
7156
|
void main() {
|
|
6550
7157
|
vec3 normal = normalize(v_normal);
|
|
7158
|
+
// A narrow lighting transition softens the roof/wall junction without moving the roof
|
|
7159
|
+
// or adding a bright outline. Exterior vertical corners are rounded in the mesh itself.
|
|
7160
|
+
if (v_building.z > 0.5 && u_edge_radius > 0.0) {
|
|
7161
|
+
float radius = min(u_edge_radius, max((v_facade.z - v_base) * 0.25, 0.01));
|
|
7162
|
+
float rim = 1.0 - smoothstep(0.0, radius, v_facade.z - v_facade.y);
|
|
7163
|
+
normal = normalize(mix(normal, vec3(0.0, 0.0, 1.0), rim));
|
|
7164
|
+
}
|
|
6551
7165
|
float lit = dot(normal, u_sun_dir) > 0.0 ? visibility(v_shadow, 0.3) : 0.0;
|
|
6552
|
-
vec3 colour = mix(
|
|
7166
|
+
vec3 colour = mix(styleColour(v_colour, normal) * (1.0 - u_shadow * (1.0 - lit)),
|
|
6553
7167
|
v_colour * shade(normal, lit), u_sun_lighting);
|
|
7168
|
+
colour *= facadeShade();
|
|
6554
7169
|
// Roof outlines and outer corners catch the light, so where one building ends and the next starts
|
|
6555
7170
|
// stays legible, at night most of all.
|
|
6556
7171
|
float edge = u_edge > 0.0 ? edgeOf() * u_edge : 0.0;
|
|
@@ -6560,13 +7175,6 @@ void main() {
|
|
|
6560
7175
|
float up = v_facade.y;
|
|
6561
7176
|
float top = v_facade.z;
|
|
6562
7177
|
float seed = v_building.x;
|
|
6563
|
-
float foot = 1.0 - u_occlusion * pow(1.0 - clamp(up / 9.0, 0.0, 1.0), 2.0);
|
|
6564
|
-
float along = v_facade.x - v_wall.x;
|
|
6565
|
-
float corner = 0.0;
|
|
6566
|
-
if (mod(v_wall.y, 2.0) >= 1.0) corner = 1.0 - smoothstep(0.0, u_corner, along);
|
|
6567
|
-
if (mod(floor(v_wall.y / 2.0), 2.0) >= 1.0)
|
|
6568
|
-
corner = max(corner, 1.0 - smoothstep(0.0, u_corner, v_wall.z - along));
|
|
6569
|
-
colour *= foot * (1.0 - u_occlusion * 0.7 * corner * mix(1.0, 0.5, clamp(up / 30.0, 0.0, 1.0)));
|
|
6570
7178
|
// Sheds, barns, factories and churches have no rows of windows; homes light more of theirs at
|
|
6571
7179
|
// night than offices, in a warm light, and offices more of theirs in a cool one.
|
|
6572
7180
|
float group = groupOf(v_building.y);
|
|
@@ -6649,7 +7257,7 @@ var tileflowBuildingGroundFragment = `#version 300 es
|
|
|
6649
7257
|
precision highp float;
|
|
6650
7258
|
in vec3 v_shadow;
|
|
6651
7259
|
uniform vec3 u_ground_shadow;
|
|
6652
|
-
${
|
|
7260
|
+
${tileflowShadowLookup}
|
|
6653
7261
|
out vec4 fragColor;
|
|
6654
7262
|
void main() {
|
|
6655
7263
|
float shaded = u_shadow * (1.0 - visibility(v_shadow, 0.15));
|
|
@@ -6681,18 +7289,6 @@ void main() {
|
|
|
6681
7289
|
v_out = a_flags.x;
|
|
6682
7290
|
v_strength = p;
|
|
6683
7291
|
}`;
|
|
6684
|
-
var tileflowBuildingBandFragment = `#version 300 es
|
|
6685
|
-
precision highp float;
|
|
6686
|
-
in float v_out;
|
|
6687
|
-
in float v_strength;
|
|
6688
|
-
in vec3 v_flood;
|
|
6689
|
-
in vec3 v_shop;
|
|
6690
|
-
uniform float u_band_strength;
|
|
6691
|
-
out vec4 fragColor;
|
|
6692
|
-
void main() {
|
|
6693
|
-
float near = 1.0 - v_out;
|
|
6694
|
-
fragColor = vec4(vec3(1.0 - u_band_strength * near * near * v_strength), 1.0);
|
|
6695
|
-
}`;
|
|
6696
7292
|
var tileflowBuildingFloodFragment = `#version 300 es
|
|
6697
7293
|
precision highp float;
|
|
6698
7294
|
in float v_out;
|
|
@@ -6707,6 +7303,50 @@ void main() {
|
|
|
6707
7303
|
fragColor = vec4((v_flood * 0.35 * near * near + v_shop * 0.12 * close) * u_flood * v_strength, 0.0);
|
|
6708
7304
|
}`;
|
|
6709
7305
|
|
|
7306
|
+
// src/buildings-3d-view.ts
|
|
7307
|
+
function buildingBoxVisible(matrix, bounds) {
|
|
7308
|
+
for (let axis = 0; axis < 3; axis += 1)
|
|
7309
|
+
for (const sign of [-1, 1]) {
|
|
7310
|
+
const a = matrix[3] + sign * matrix[axis];
|
|
7311
|
+
const b2 = matrix[7] + sign * matrix[axis + 4];
|
|
7312
|
+
const c = matrix[11] + sign * matrix[axis + 8];
|
|
7313
|
+
const d2 = matrix[15] + sign * matrix[axis + 12];
|
|
7314
|
+
const distance = a * bounds[a < 0 ? 0 : 3] + b2 * bounds[b2 < 0 ? 1 : 4] + c * bounds[c < 0 ? 2 : 5] + d2;
|
|
7315
|
+
if (!Number.isFinite(distance)) return true;
|
|
7316
|
+
if (distance < -1e-7) return false;
|
|
7317
|
+
}
|
|
7318
|
+
return true;
|
|
7319
|
+
}
|
|
7320
|
+
function buildingDrawRanges(mesh, visible, wallsOnly = false) {
|
|
7321
|
+
if (!mesh.groups)
|
|
7322
|
+
return [
|
|
7323
|
+
{
|
|
7324
|
+
from: wallsOnly ? mesh.wallsFrom : 0,
|
|
7325
|
+
count: mesh.indices.length - (wallsOnly ? mesh.wallsFrom : 0)
|
|
7326
|
+
}
|
|
7327
|
+
];
|
|
7328
|
+
const groups = mesh.groups.filter(visible);
|
|
7329
|
+
const ranges = [];
|
|
7330
|
+
const append = (from, count) => {
|
|
7331
|
+
if (!count) return;
|
|
7332
|
+
const last = ranges.at(-1);
|
|
7333
|
+
if (last && last.from + last.count === from) last.count += count;
|
|
7334
|
+
else ranges.push({ from, count });
|
|
7335
|
+
};
|
|
7336
|
+
if (!wallsOnly) for (const group of groups) append(group.roofsFrom, group.roofsCount);
|
|
7337
|
+
for (const group of groups) append(group.wallsFrom, group.wallsCount);
|
|
7338
|
+
return ranges;
|
|
7339
|
+
}
|
|
7340
|
+
function buildingCornerDetail(zoom, latitude = 40.42, radius = 0.8, pitch = 0, previous) {
|
|
7341
|
+
if (pitch > 70) return 2;
|
|
7342
|
+
const metresPerPixel = 40075016686e-3 * Math.max(0.01, Math.cos(latitude * Math.PI / 180)) / (512 * 2 ** zoom);
|
|
7343
|
+
const pixels = radius / metresPerPixel * (1 + Math.sin(Math.max(0, pitch) * Math.PI / 180));
|
|
7344
|
+
if (previous === 0 && pixels < 0.66) return 0;
|
|
7345
|
+
if (previous === 1 && pixels >= 0.54 && pixels < 3.3) return 1;
|
|
7346
|
+
if (previous === 2 && pixels >= 2.7) return 2;
|
|
7347
|
+
return pixels < 0.6 ? 0 : pixels < 3 ? 1 : 2;
|
|
7348
|
+
}
|
|
7349
|
+
|
|
6710
7350
|
// ../../node_modules/.pnpm/fflate@0.8.3/node_modules/fflate/esm/browser.js
|
|
6711
7351
|
var u8 = Uint8Array;
|
|
6712
7352
|
var u16 = Uint16Array;
|
|
@@ -8202,7 +8842,7 @@ function createCameraTiles(options) {
|
|
|
8202
8842
|
tiles.set(key, entry);
|
|
8203
8843
|
}
|
|
8204
8844
|
entry.used = now;
|
|
8205
|
-
if (!entry.value && !entry.loading && (entry.retryAt ?? 0) <= now)
|
|
8845
|
+
if ((!entry.value || entry.retryAt !== void 0) && !entry.loading && (entry.retryAt ?? 0) <= now)
|
|
8206
8846
|
load(source, key, entry, tileMetres);
|
|
8207
8847
|
}
|
|
8208
8848
|
evict();
|
|
@@ -8211,17 +8851,26 @@ function createCameraTiles(options) {
|
|
|
8211
8851
|
function load(source, key, entry, tileMetres) {
|
|
8212
8852
|
const controller = new AbortController();
|
|
8213
8853
|
entry.loading = controller;
|
|
8854
|
+
entry.retryAt = void 0;
|
|
8214
8855
|
source.readTile({ z: zoom, x: entry.x, y: entry.y }, { signal: controller.signal }).then((tile) => {
|
|
8215
|
-
entry.loading
|
|
8216
|
-
if (disposed || tiles.get(key) !== entry) return;
|
|
8856
|
+
if (disposed || tiles.get(key) !== entry || entry.loading !== controller) return;
|
|
8217
8857
|
if (!tile) {
|
|
8218
8858
|
if (entry.value && entry.value !== options.empty) options.release(entry.value);
|
|
8219
8859
|
entry.value = options.empty;
|
|
8860
|
+
entry.loading = void 0;
|
|
8861
|
+
options.changed();
|
|
8862
|
+
map.triggerRepaint();
|
|
8220
8863
|
return;
|
|
8221
8864
|
}
|
|
8222
|
-
queue.
|
|
8865
|
+
for (let at = queue.length - 1; at >= 0; at -= 1) {
|
|
8866
|
+
const queued = queue[at];
|
|
8867
|
+
if (queued.entry.loading !== queued.controller || queued.controller.signal.aborted)
|
|
8868
|
+
queue.splice(at, 1);
|
|
8869
|
+
}
|
|
8870
|
+
queue.push({ key, entry, controller, tile, metres: tileMetres });
|
|
8223
8871
|
drain();
|
|
8224
8872
|
}).catch(() => {
|
|
8873
|
+
if (disposed || entry.loading !== controller) return;
|
|
8225
8874
|
entry.loading = void 0;
|
|
8226
8875
|
entry.retryAt = performance.now() + 15e3;
|
|
8227
8876
|
});
|
|
@@ -8229,29 +8878,46 @@ function createCameraTiles(options) {
|
|
|
8229
8878
|
function drain() {
|
|
8230
8879
|
if (building || disposed) return;
|
|
8231
8880
|
building = true;
|
|
8232
|
-
const step = () => {
|
|
8881
|
+
const step = async () => {
|
|
8233
8882
|
const started = performance.now();
|
|
8234
8883
|
let built = false;
|
|
8235
|
-
while (queue.length && performance.now() - started < buildBudgetMs) {
|
|
8236
|
-
const { key, tile, metres: tileMetres } = queue.shift();
|
|
8237
|
-
const
|
|
8238
|
-
if (!
|
|
8239
|
-
|
|
8884
|
+
while (!disposed && queue.length && performance.now() - started < buildBudgetMs) {
|
|
8885
|
+
const { key, entry, controller, tile, metres: tileMetres } = queue.shift();
|
|
8886
|
+
const current = () => !disposed && tiles.get(key) === entry && entry.loading === controller && !controller.signal.aborted;
|
|
8887
|
+
if (!current()) continue;
|
|
8888
|
+
let value;
|
|
8240
8889
|
try {
|
|
8241
|
-
|
|
8890
|
+
value = await options.build(tile, tileMetres, controller.signal);
|
|
8242
8891
|
} catch {
|
|
8243
|
-
|
|
8892
|
+
if (current()) {
|
|
8893
|
+
entry.loading = void 0;
|
|
8894
|
+
entry.retryAt = performance.now() + 15e3;
|
|
8895
|
+
}
|
|
8896
|
+
continue;
|
|
8244
8897
|
}
|
|
8245
|
-
if (
|
|
8246
|
-
options.release(
|
|
8898
|
+
if (!current()) {
|
|
8899
|
+
if (value !== options.empty) options.release(value);
|
|
8900
|
+
continue;
|
|
8901
|
+
}
|
|
8902
|
+
const previous = entry.value;
|
|
8903
|
+
entry.value = value;
|
|
8904
|
+
entry.loading = void 0;
|
|
8905
|
+
if (previous && previous !== options.empty && previous !== value) options.release(previous);
|
|
8247
8906
|
built = true;
|
|
8248
8907
|
}
|
|
8249
|
-
if (built)
|
|
8250
|
-
|
|
8251
|
-
|
|
8908
|
+
if (built && !disposed) {
|
|
8909
|
+
options.changed();
|
|
8910
|
+
map.triggerRepaint();
|
|
8911
|
+
}
|
|
8912
|
+
if (queue.length && !disposed)
|
|
8913
|
+
cameraFrame(() => {
|
|
8914
|
+
void step();
|
|
8915
|
+
});
|
|
8252
8916
|
else building = false;
|
|
8253
8917
|
};
|
|
8254
|
-
cameraFrame(
|
|
8918
|
+
cameraFrame(() => {
|
|
8919
|
+
void step();
|
|
8920
|
+
});
|
|
8255
8921
|
}
|
|
8256
8922
|
function evict() {
|
|
8257
8923
|
const keep = new Set(wanted);
|
|
@@ -8910,23 +9576,8 @@ uniform vec3 u_sun_dir;
|
|
|
8910
9576
|
uniform vec3 u_sun_light;
|
|
8911
9577
|
uniform vec3 u_sky_light;
|
|
8912
9578
|
uniform float u_night;
|
|
8913
|
-
uniform highp sampler2DShadow u_shadow_map;
|
|
8914
|
-
uniform vec2 u_shadow_texel;
|
|
8915
|
-
uniform float u_shadow;
|
|
8916
|
-
uniform float u_depth_unit;
|
|
8917
9579
|
out vec4 fragColor;
|
|
8918
|
-
|
|
8919
|
-
float visibility(vec3 c, float bias) {
|
|
8920
|
-
if (u_shadow <= 0.0 || c.x <= 0.0 || c.y <= 0.0 || c.x >= 1.0 || c.y >= 1.0 || c.z >= 1.0)
|
|
8921
|
-
return 1.0;
|
|
8922
|
-
float z = c.z - bias * u_depth_unit;
|
|
8923
|
-
vec2 o = u_shadow_texel * 0.75;
|
|
8924
|
-
return 0.25 * (
|
|
8925
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, -o.y), z)) +
|
|
8926
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, -o.y), z)) +
|
|
8927
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, o.y), z)) +
|
|
8928
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, o.y), z)));
|
|
8929
|
-
}
|
|
9580
|
+
${tileflowShadowLookup}
|
|
8930
9581
|
|
|
8931
9582
|
// The buildings' light: a sunlit roof keeps its colour, a face turned to a low sun glows, and shade
|
|
8932
9583
|
// takes the sky's colour; a face that looks down sees little sky.
|
|
@@ -9356,10 +10007,9 @@ function releaseBuffers(context, entry) {
|
|
|
9356
10007
|
|
|
9357
10008
|
// src/scene-3d-browser.ts
|
|
9358
10009
|
var tileflowSceneExtent = 8192;
|
|
9359
|
-
var tileflowSceneShadowFloor =
|
|
9360
|
-
var shadowFade = [0.5, 7];
|
|
10010
|
+
var tileflowSceneShadowFloor = 15;
|
|
9361
10011
|
function tileflowSceneLight(sun, shadows = true) {
|
|
9362
|
-
const linear = (value) => value
|
|
10012
|
+
const linear = (value) => value ** 2.2;
|
|
9363
10013
|
const sunLight = sun.rgb.map((channel) => linear(channel) * sun.intensity);
|
|
9364
10014
|
const skyLight = sun.ambient.rgb.map((channel) => linear(channel) * sun.ambient.intensity);
|
|
9365
10015
|
const up = sun.direction[2];
|
|
@@ -9374,11 +10024,7 @@ function tileflowSceneLight(sun, shadows = true) {
|
|
|
9374
10024
|
Math.cos(azimuth) * Math.cos(floor),
|
|
9375
10025
|
Math.sin(floor)
|
|
9376
10026
|
];
|
|
9377
|
-
const
|
|
9378
|
-
1,
|
|
9379
|
-
Math.max(0, (sun.altitude - shadowFade[0]) / (shadowFade[1] - shadowFade[0]))
|
|
9380
|
-
);
|
|
9381
|
-
const strength = shadows ? Math.min(1, sun.shadowIntensity) * Math.min(1, sun.intensity * 4) * low * low * (3 - 2 * low) : 0;
|
|
10027
|
+
const strength = shadows && sun.intensity > 0 ? Math.min(1, sun.shadowIntensity) : 0;
|
|
9382
10028
|
const night = Math.min(1, Math.max(0, (0.14 - sun.brightness) / 0.09));
|
|
9383
10029
|
return {
|
|
9384
10030
|
direction: sun.direction,
|
|
@@ -9394,12 +10040,12 @@ var scenes = /* @__PURE__ */ new WeakMap();
|
|
|
9394
10040
|
function tileflowScene3D(map) {
|
|
9395
10041
|
let scene = scenes.get(map);
|
|
9396
10042
|
if (!scene) {
|
|
9397
|
-
scene = createScene();
|
|
10043
|
+
scene = createScene(map);
|
|
9398
10044
|
scenes.set(map, scene);
|
|
9399
10045
|
}
|
|
9400
10046
|
return scene;
|
|
9401
10047
|
}
|
|
9402
|
-
function createScene() {
|
|
10048
|
+
function createScene(map) {
|
|
9403
10049
|
const casters = /* @__PURE__ */ new Set();
|
|
9404
10050
|
let target;
|
|
9405
10051
|
let key;
|
|
@@ -9439,6 +10085,7 @@ function createScene() {
|
|
|
9439
10085
|
return target;
|
|
9440
10086
|
}
|
|
9441
10087
|
function prepare(context, light, metres) {
|
|
10088
|
+
if (light.strength <= 0) return;
|
|
9442
10089
|
const shadow = ensureTarget(context);
|
|
9443
10090
|
if (shadow.failed) return;
|
|
9444
10091
|
const casting = [];
|
|
@@ -9446,7 +10093,6 @@ function createScene() {
|
|
|
9446
10093
|
const bounds = caster.bounds();
|
|
9447
10094
|
if (bounds) casting.push({ caster, bounds });
|
|
9448
10095
|
}
|
|
9449
|
-
if (light.strength <= 0) return;
|
|
9450
10096
|
if (casting.length === 0) {
|
|
9451
10097
|
frame = void 0;
|
|
9452
10098
|
key = void 0;
|
|
@@ -9463,6 +10109,21 @@ function createScene() {
|
|
|
9463
10109
|
tallest = Math.max(tallest, bounds.tallest);
|
|
9464
10110
|
}
|
|
9465
10111
|
const origin = [left, top];
|
|
10112
|
+
const scale = metres;
|
|
10113
|
+
const unit2 = tileflowSceneExtent * scale;
|
|
10114
|
+
const lightView = tileflowSunLightMatrix(
|
|
10115
|
+
light.shadowDirection,
|
|
10116
|
+
{
|
|
10117
|
+
min: [0, -(bottom + 1 - top) * unit2, 0],
|
|
10118
|
+
max: [(right + 1 - left) * unit2, 0, tallest]
|
|
10119
|
+
},
|
|
10120
|
+
{
|
|
10121
|
+
receivers: viewportReceivers(map, zoom, origin, scale, tallest),
|
|
10122
|
+
resolution: shadow.size,
|
|
10123
|
+
origin: [origin[0] * unit2, -origin[1] * unit2, 0],
|
|
10124
|
+
gridScale: unit2 * 2 ** zoom
|
|
10125
|
+
}
|
|
10126
|
+
);
|
|
9466
10127
|
const next = [
|
|
9467
10128
|
...light.shadowDirection.map((value) => value.toFixed(5)),
|
|
9468
10129
|
zoom,
|
|
@@ -9471,15 +10132,11 @@ function createScene() {
|
|
|
9471
10132
|
right,
|
|
9472
10133
|
bottom,
|
|
9473
10134
|
tallest.toFixed(2),
|
|
10135
|
+
scale,
|
|
10136
|
+
...lightView.matrix,
|
|
9474
10137
|
...casting.map(({ caster }) => caster.key())
|
|
9475
10138
|
].join();
|
|
9476
10139
|
if (next === key && frame) return;
|
|
9477
|
-
const scale = metres;
|
|
9478
|
-
const unit2 = tileflowSceneExtent * scale;
|
|
9479
|
-
const lightView = tileflowSunLightMatrix(light.shadowDirection, {
|
|
9480
|
-
min: [0, -(bottom + 1 - top) * unit2, 0],
|
|
9481
|
-
max: [(right + 1 - left) * unit2, 0, tallest]
|
|
9482
|
-
});
|
|
9483
10140
|
const offsetOf = (tile, tileZoom = zoom) => {
|
|
9484
10141
|
const factor = 2 ** (zoom - tileZoom);
|
|
9485
10142
|
return [
|
|
@@ -9542,18 +10199,22 @@ function createScene() {
|
|
|
9542
10199
|
const on = strength > 0 && frame && target && !target.failed;
|
|
9543
10200
|
context.activeTexture(context.TEXTURE0);
|
|
9544
10201
|
context.bindTexture(context.TEXTURE_2D, target?.texture ?? null);
|
|
9545
|
-
context.uniform1i(uniforms.u_shadow_map, 0);
|
|
9546
|
-
context.uniform1f(uniforms.u_shadow, on ? strength : 0);
|
|
10202
|
+
context.uniform1i(uniforms.u_shadow_map ?? null, 0);
|
|
10203
|
+
context.uniform1f(uniforms.u_shadow ?? null, on ? strength : 0);
|
|
9547
10204
|
if (!on || !frame || !target) return;
|
|
9548
|
-
context.uniformMatrix4fv(uniforms.u_light_matrix, false, frame.light.matrix);
|
|
9549
|
-
context.
|
|
9550
|
-
context.uniform1f(
|
|
9551
|
-
|
|
10205
|
+
context.uniformMatrix4fv(uniforms.u_light_matrix ?? null, false, frame.light.matrix);
|
|
10206
|
+
context.uniform1f(uniforms.u_depth_unit ?? null, 1 / frame.light.depth);
|
|
10207
|
+
context.uniform1f(
|
|
10208
|
+
uniforms.u_normal_offset ?? null,
|
|
10209
|
+
Math.min(0.15, Math.max(0.02, frame.light.extent / target.size * 0.5))
|
|
10210
|
+
);
|
|
9552
10211
|
},
|
|
9553
10212
|
drawsGround(caster) {
|
|
9554
10213
|
let first;
|
|
9555
|
-
for (const entry of casters)
|
|
10214
|
+
for (const entry of casters) {
|
|
10215
|
+
if (!entry.bounds()) continue;
|
|
9556
10216
|
if (!first || entry.groundOrder < first.groundOrder) first = entry;
|
|
10217
|
+
}
|
|
9557
10218
|
return first === caster;
|
|
9558
10219
|
},
|
|
9559
10220
|
release(context) {
|
|
@@ -9564,9 +10225,86 @@ function createScene() {
|
|
|
9564
10225
|
frame = void 0;
|
|
9565
10226
|
key = void 0;
|
|
9566
10227
|
}
|
|
9567
|
-
};
|
|
10228
|
+
};
|
|
10229
|
+
}
|
|
10230
|
+
function viewportReceivers(input, zoom, origin, metres, tallest) {
|
|
10231
|
+
const map = input;
|
|
10232
|
+
try {
|
|
10233
|
+
if (typeof map.getCanvas !== "function" || typeof map.getCenter !== "function" || typeof map.getBearing !== "function" || typeof map.getPitch !== "function" || typeof map.getZoom !== "function" || typeof map.getVerticalFieldOfView !== "function" || typeof map.project !== "function" || typeof map.unproject !== "function")
|
|
10234
|
+
return void 0;
|
|
10235
|
+
if (map.getTerrain?.()) return void 0;
|
|
10236
|
+
if (map.getCenterElevation && Math.abs(map.getCenterElevation()) > 1e-5) return void 0;
|
|
10237
|
+
const { clientWidth: width, clientHeight: height } = map.getCanvas();
|
|
10238
|
+
const pitch = map.getPitch();
|
|
10239
|
+
const fov = map.getVerticalFieldOfView();
|
|
10240
|
+
if (!(width > 0 && height > 0 && fov > 0 && fov < 90 && pitch >= 0 && pitch + fov / 2 < 85))
|
|
10241
|
+
return void 0;
|
|
10242
|
+
const count = 2 ** zoom;
|
|
10243
|
+
const unit2 = tileflowSceneExtent * metres;
|
|
10244
|
+
const centre = map.getCenter();
|
|
10245
|
+
const centreX = (centre.lng + 180) / 360 * count;
|
|
10246
|
+
const point = ({ lng, lat }) => {
|
|
10247
|
+
if (!Number.isFinite(lng) || !Number.isFinite(lat) || Math.abs(lat) >= 85.051129)
|
|
10248
|
+
throw new Error("Unprojected shadow receiver is outside Mercator.");
|
|
10249
|
+
const x3 = (lng + 180) / 360 * count;
|
|
10250
|
+
const wrappedX = x3 + Math.round((centreX - x3) / count) * count;
|
|
10251
|
+
const phi = lat * Math.PI / 180;
|
|
10252
|
+
const y2 = (1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 * count;
|
|
10253
|
+
return [(wrappedX - origin[0]) * unit2, -(y2 - origin[1]) * unit2, 0];
|
|
10254
|
+
};
|
|
10255
|
+
const pixels = [
|
|
10256
|
+
[0, 0],
|
|
10257
|
+
[width, 0],
|
|
10258
|
+
[width, height],
|
|
10259
|
+
[0, height]
|
|
10260
|
+
];
|
|
10261
|
+
const ground = pixels.map((pixel2) => point(map.unproject(pixel2)));
|
|
10262
|
+
const centrePoint = point(centre);
|
|
10263
|
+
const pixel = unit2 / (512 * 2 ** (map.getZoom() - zoom));
|
|
10264
|
+
const distance = height * pixel / (2 * Math.tan(fov * Math.PI / 360));
|
|
10265
|
+
const angle = pitch * Math.PI / 180;
|
|
10266
|
+
const bearing = map.getBearing() * Math.PI / 180;
|
|
10267
|
+
const elevation = distance * Math.cos(angle);
|
|
10268
|
+
if (!(elevation > 0.1 && Number.isFinite(elevation))) return void 0;
|
|
10269
|
+
const camera = [
|
|
10270
|
+
centrePoint[0] - distance * Math.sin(angle) * Math.sin(bearing),
|
|
10271
|
+
centrePoint[1] - distance * Math.sin(angle) * Math.cos(bearing),
|
|
10272
|
+
elevation
|
|
10273
|
+
];
|
|
10274
|
+
const screenCentre = map.project(centre);
|
|
10275
|
+
const right = [Math.cos(bearing), -Math.sin(bearing), 0];
|
|
10276
|
+
const forward = [
|
|
10277
|
+
Math.sin(bearing) * Math.sin(angle),
|
|
10278
|
+
Math.cos(bearing) * Math.sin(angle),
|
|
10279
|
+
-Math.cos(angle)
|
|
10280
|
+
];
|
|
10281
|
+
const up = [
|
|
10282
|
+
Math.sin(bearing) * Math.cos(angle),
|
|
10283
|
+
Math.cos(bearing) * Math.cos(angle),
|
|
10284
|
+
Math.sin(angle)
|
|
10285
|
+
];
|
|
10286
|
+
const dot3 = (a, b2) => a.reduce((sum, value, index) => sum + value * b2[index], 0);
|
|
10287
|
+
for (const [index, corner] of ground.entries()) {
|
|
10288
|
+
const ray = corner.map((value, axis) => value - camera[axis]);
|
|
10289
|
+
const depth = dot3(ray, forward);
|
|
10290
|
+
if (!(depth > 0)) return void 0;
|
|
10291
|
+
const x3 = screenCentre.x + dot3(ray, right) / depth * (distance / pixel);
|
|
10292
|
+
const y2 = screenCentre.y - dot3(ray, up) / depth * (distance / pixel);
|
|
10293
|
+
if (!Number.isFinite(x3) || !Number.isFinite(y2) || Math.abs(x3 - pixels[index][0]) > Math.max(2, width * 5e-3) || Math.abs(y2 - pixels[index][1]) > Math.max(2, height * 5e-3))
|
|
10294
|
+
return void 0;
|
|
10295
|
+
}
|
|
10296
|
+
const fraction2 = Math.min(Math.max(0, tallest) / elevation, 1);
|
|
10297
|
+
return [
|
|
10298
|
+
...ground,
|
|
10299
|
+
...ground.map(
|
|
10300
|
+
(corner) => corner.map((value, index) => value + (camera[index] - value) * fraction2)
|
|
10301
|
+
)
|
|
10302
|
+
];
|
|
10303
|
+
} catch {
|
|
10304
|
+
return void 0;
|
|
10305
|
+
}
|
|
9568
10306
|
}
|
|
9569
|
-
function tileflowSunLightMatrix(direction, box) {
|
|
10307
|
+
function tileflowSunLightMatrix(direction, box, view = {}) {
|
|
9570
10308
|
const length = Math.hypot(direction[0], direction[1], direction[2]) || 1;
|
|
9571
10309
|
const towards = direction.map((value) => value / length);
|
|
9572
10310
|
const forward = towards.map((value) => -value);
|
|
@@ -9583,24 +10321,44 @@ function tileflowSunLightMatrix(direction, box) {
|
|
|
9583
10321
|
const right = normalize2(cross(forward, hint));
|
|
9584
10322
|
const up = cross(right, forward);
|
|
9585
10323
|
const dot3 = (a, b2) => a[0] * b2[0] + a[1] * b2[1] + a[2] * b2[2];
|
|
9586
|
-
const
|
|
10324
|
+
const corners = [];
|
|
10325
|
+
for (const x3 of [box.min[0], box.max[0]])
|
|
10326
|
+
for (const y2 of [box.min[1], box.max[1]])
|
|
10327
|
+
for (const z2 of [box.min[2], box.max[2]]) corners.push([x3, y2, z2]);
|
|
10328
|
+
const range = (axis, points) => {
|
|
9587
10329
|
let low = Infinity;
|
|
9588
10330
|
let high = -Infinity;
|
|
9589
|
-
for (const
|
|
9590
|
-
|
|
9591
|
-
|
|
9592
|
-
|
|
9593
|
-
|
|
9594
|
-
high = Math.max(high, value);
|
|
9595
|
-
}
|
|
10331
|
+
for (const point of points) {
|
|
10332
|
+
const value = dot3(point, axis);
|
|
10333
|
+
low = Math.min(low, value);
|
|
10334
|
+
high = Math.max(high, value);
|
|
10335
|
+
}
|
|
9596
10336
|
return [low, high];
|
|
9597
10337
|
};
|
|
9598
10338
|
const pad = ([low, high], extra) => [low - extra, high + extra];
|
|
9599
|
-
const
|
|
9600
|
-
const
|
|
9601
|
-
|
|
9602
|
-
|
|
9603
|
-
|
|
10339
|
+
const receivers = view.receivers?.length ? view.receivers : corners;
|
|
10340
|
+
const fit = (axis) => {
|
|
10341
|
+
const [low, high] = range(axis, receivers);
|
|
10342
|
+
const extra = (high - low) * 0.01 + 2;
|
|
10343
|
+
if (!(view.resolution && view.resolution >= 1)) return pad([low, high], extra);
|
|
10344
|
+
const span = high - low + extra * 2;
|
|
10345
|
+
const gridScale = view.gridScale && view.gridScale > 0 ? view.gridScale : 1;
|
|
10346
|
+
const gridSpan = span / gridScale;
|
|
10347
|
+
const quantum = 2 ** Math.floor(Math.log2(gridSpan)) / 16;
|
|
10348
|
+
const extent5 = Math.ceil(gridSpan / quantum) * quantum * gridScale;
|
|
10349
|
+
const texel = extent5 / view.resolution;
|
|
10350
|
+
const offset = dot3(view.origin ?? [0, 0, 0], axis);
|
|
10351
|
+
const centre = Math.round(((low + high) / 2 + offset) / texel) * texel - offset;
|
|
10352
|
+
return [centre - extent5 / 2, centre + extent5 / 2];
|
|
10353
|
+
};
|
|
10354
|
+
const [a0, a1] = fit(right);
|
|
10355
|
+
const [b0, b1] = fit(up);
|
|
10356
|
+
const endpoints = towards[2] > 1e-5 ? corners.map(([x3, y2, z2]) => [
|
|
10357
|
+
x3 - z2 * towards[0] / towards[2],
|
|
10358
|
+
y2 - z2 * towards[1] / towards[2],
|
|
10359
|
+
0
|
|
10360
|
+
]) : [];
|
|
10361
|
+
const [c0, c1] = pad(range(forward, [...corners, ...endpoints, ...receivers]), 10);
|
|
9604
10362
|
const hu = (a1 - a0) / 2;
|
|
9605
10363
|
const hv = (b1 - b0) / 2;
|
|
9606
10364
|
const hd = (c1 - c0) / 2;
|
|
@@ -9616,13 +10374,19 @@ function tileflowSunLightMatrix(direction, box) {
|
|
|
9616
10374
|
return { matrix, depth: hd * 2, extent: Math.max(hu, hv) * 2 };
|
|
9617
10375
|
}
|
|
9618
10376
|
|
|
10377
|
+
// src/scene-3d-visibility.ts
|
|
10378
|
+
var tileflowSceneMinzoom = 15;
|
|
10379
|
+
var tileflowSceneFadeZooms = 0.25;
|
|
10380
|
+
function tileflowSceneOpacity(zoom, minzoom = tileflowSceneMinzoom) {
|
|
10381
|
+
return Math.min(1, Math.max(0, (zoom - minzoom) / tileflowSceneFadeZooms));
|
|
10382
|
+
}
|
|
10383
|
+
|
|
9619
10384
|
// src/buildings-3d-browser.ts
|
|
9620
10385
|
init_sun();
|
|
9621
10386
|
init_theme_blend();
|
|
9622
10387
|
var tileflowBuildings3DLayerId = "tileflow-buildings-3d";
|
|
9623
10388
|
var preferredZoom = 15;
|
|
9624
10389
|
var defaultTransition = 0;
|
|
9625
|
-
var occlusion = { ground: 8, groundStrength: 0.42, wall: 0.36, corner: 3 };
|
|
9626
10390
|
var flood = { reach: 10, colour: [1, 0.7, 0.4] };
|
|
9627
10391
|
var edgeLine = { width: 1, day: 0.5 };
|
|
9628
10392
|
var paletteWidth = 256;
|
|
@@ -9636,15 +10400,17 @@ var derivedParts = {
|
|
|
9636
10400
|
};
|
|
9637
10401
|
function createTileflowBuildings3D(options) {
|
|
9638
10402
|
const { map } = options;
|
|
9639
|
-
const minzoom = options.minzoom ??
|
|
10403
|
+
const minzoom = options.minzoom ?? tileflowSceneMinzoom;
|
|
9640
10404
|
const maxTiles = Math.round(options.maxTiles ?? 25);
|
|
9641
10405
|
if (!Number.isFinite(minzoom) || minzoom < 0 || minzoom > 24)
|
|
9642
10406
|
throw new RangeError("Tileflow 3D buildings minzoom must be a zoom from 0 to 24.");
|
|
9643
10407
|
if (!(maxTiles >= 1 && maxTiles <= 64))
|
|
9644
10408
|
throw new RangeError("Tileflow 3D buildings maxTiles must be from 1 to 64.");
|
|
9645
|
-
|
|
10409
|
+
const mesher = createBuildingMesher();
|
|
10410
|
+
let detail = buildingCornerDetail(map.getZoom());
|
|
10411
|
+
let scale = 1;
|
|
9646
10412
|
let opacity = 1;
|
|
9647
|
-
let target =
|
|
10413
|
+
let target = 1;
|
|
9648
10414
|
let blank;
|
|
9649
10415
|
let motion;
|
|
9650
10416
|
let gl;
|
|
@@ -9652,6 +10418,7 @@ function createTileflowBuildings3D(options) {
|
|
|
9652
10418
|
let variant;
|
|
9653
10419
|
let linking;
|
|
9654
10420
|
let ground;
|
|
10421
|
+
const contactMask = createBuildingContactMask();
|
|
9655
10422
|
let disposed = false;
|
|
9656
10423
|
const scene = tileflowScene3D(map);
|
|
9657
10424
|
let generation = 0;
|
|
@@ -9705,6 +10472,7 @@ function createTileflowBuildings3D(options) {
|
|
|
9705
10472
|
context.deleteVertexArray(ground.vao);
|
|
9706
10473
|
context.deleteBuffer(ground.vbo);
|
|
9707
10474
|
}
|
|
10475
|
+
contactMask.release(context);
|
|
9708
10476
|
if (blank) context.deleteTexture(blank);
|
|
9709
10477
|
blank = void 0;
|
|
9710
10478
|
programs = {};
|
|
@@ -9740,12 +10508,15 @@ function createTileflowBuildings3D(options) {
|
|
|
9740
10508
|
} catch {
|
|
9741
10509
|
return;
|
|
9742
10510
|
}
|
|
9743
|
-
if (
|
|
9744
|
-
|
|
10511
|
+
if (target > 0 || scale > 0) upload(context);
|
|
10512
|
+
if (visibleScale() <= 0) {
|
|
10513
|
+
contactMask.release(context);
|
|
10514
|
+
return;
|
|
10515
|
+
}
|
|
9745
10516
|
draw(context, args);
|
|
9746
10517
|
}
|
|
9747
10518
|
};
|
|
9748
|
-
const makeCamera = (layers, source) => createCameraTiles({
|
|
10519
|
+
const makeCamera = (layers, source, edgeRadius = 0) => createCameraTiles({
|
|
9749
10520
|
map,
|
|
9750
10521
|
source,
|
|
9751
10522
|
layers: [layers.building, ...layers.places ? [layers.places] : []],
|
|
@@ -9754,16 +10525,22 @@ function createTileflowBuildings3D(options) {
|
|
|
9754
10525
|
maxTiles,
|
|
9755
10526
|
...options.transformRequest ? { transformRequest: options.transformRequest } : {},
|
|
9756
10527
|
active: () => target > 0 || scale > 0,
|
|
9757
|
-
build(tile, tileMetres) {
|
|
10528
|
+
async build(tile, tileMetres, signal) {
|
|
9758
10529
|
const decoded = tile.layers[layers.building];
|
|
9759
10530
|
if (!decoded) return emptyTile;
|
|
9760
10531
|
const places = layers.places ? tile.layers[layers.places] : void 0;
|
|
9761
10532
|
const masks = models.masksIn(tile, tileflowBuildingExtent);
|
|
9762
|
-
const mesh =
|
|
9763
|
-
|
|
9764
|
-
|
|
9765
|
-
|
|
9766
|
-
|
|
10533
|
+
const mesh = await mesher.build(
|
|
10534
|
+
decoded,
|
|
10535
|
+
{
|
|
10536
|
+
tileMetres,
|
|
10537
|
+
edgeRadius: detail === 0 ? 0 : edgeRadius,
|
|
10538
|
+
cornerAngle: detail === 1 ? Math.PI / 3 : Math.PI / 6,
|
|
10539
|
+
...places ? { places } : {},
|
|
10540
|
+
...masks ? { masks } : {}
|
|
10541
|
+
},
|
|
10542
|
+
signal
|
|
10543
|
+
);
|
|
9767
10544
|
return mesh ? { mesh } : emptyTile;
|
|
9768
10545
|
},
|
|
9769
10546
|
empty: emptyTile,
|
|
@@ -9776,26 +10553,45 @@ function createTileflowBuildings3D(options) {
|
|
|
9776
10553
|
});
|
|
9777
10554
|
let cameraKey = JSON.stringify([
|
|
9778
10555
|
tileflowDefaultBuildings3DDesign.source,
|
|
9779
|
-
tileflowDefaultBuildings3DDesign.layers
|
|
10556
|
+
tileflowDefaultBuildings3DDesign.layers,
|
|
10557
|
+
0
|
|
9780
10558
|
]);
|
|
9781
10559
|
let camera = makeCamera(
|
|
9782
10560
|
tileflowDefaultBuildings3DDesign.layers,
|
|
9783
10561
|
tileflowDefaultBuildings3DDesign.source
|
|
9784
10562
|
);
|
|
9785
10563
|
function ensureCamera() {
|
|
9786
|
-
const { source, layers } = design();
|
|
9787
|
-
const key = JSON.stringify([source, layers]);
|
|
9788
|
-
|
|
10564
|
+
const { source, layers, edgeRadius = 0 } = design();
|
|
10565
|
+
const key = JSON.stringify([source, layers, edgeRadius]);
|
|
10566
|
+
const nextDetail = buildingCornerDetail(
|
|
10567
|
+
map.getZoom(),
|
|
10568
|
+
map.getCenter().lat,
|
|
10569
|
+
edgeRadius,
|
|
10570
|
+
map.getPitch(),
|
|
10571
|
+
detail
|
|
10572
|
+
);
|
|
10573
|
+
if (key === cameraKey) {
|
|
10574
|
+
if (nextDetail !== detail && (target > 0 || scale > 0)) {
|
|
10575
|
+
detail = nextDetail;
|
|
10576
|
+
if (edgeRadius > 0) camera.refresh(() => true);
|
|
10577
|
+
}
|
|
10578
|
+
return;
|
|
10579
|
+
}
|
|
10580
|
+
detail = nextDetail;
|
|
9789
10581
|
for (const entry of camera.all()) if (entry.value && gl) release(gl, entry.value);
|
|
9790
10582
|
camera.dispose();
|
|
9791
10583
|
cameraKey = key;
|
|
9792
|
-
camera = makeCamera(layers, source);
|
|
10584
|
+
camera = makeCamera(layers, source, edgeRadius);
|
|
9793
10585
|
generation += 1;
|
|
9794
10586
|
if (target > 0) camera.update();
|
|
9795
10587
|
}
|
|
9796
10588
|
function release(context, tile) {
|
|
9797
10589
|
if (tile.gl) {
|
|
9798
10590
|
context.deleteVertexArray(tile.gl.vao);
|
|
10591
|
+
if (tile.gl.contact) {
|
|
10592
|
+
context.deleteVertexArray(tile.gl.contact.vao);
|
|
10593
|
+
context.deleteBuffer(tile.gl.contact.vbo);
|
|
10594
|
+
}
|
|
9799
10595
|
context.deleteBuffer(tile.gl.vbo);
|
|
9800
10596
|
context.deleteBuffer(tile.gl.ibo);
|
|
9801
10597
|
tile.gl = void 0;
|
|
@@ -9806,7 +10602,9 @@ function createTileflowBuildings3D(options) {
|
|
|
9806
10602
|
}
|
|
9807
10603
|
}
|
|
9808
10604
|
function upload(context) {
|
|
10605
|
+
const modelKey = models.key();
|
|
9809
10606
|
models.upload(context);
|
|
10607
|
+
if (modelKey !== models.key()) map.triggerRepaint();
|
|
9810
10608
|
let budget = 2;
|
|
9811
10609
|
for (const { value: entry } of camera.ready()) {
|
|
9812
10610
|
if (entry.gl) continue;
|
|
@@ -9839,14 +10637,18 @@ function createTileflowBuildings3D(options) {
|
|
|
9839
10637
|
context.bindVertexArray(null);
|
|
9840
10638
|
entry.gl = { vao, vbo, ibo };
|
|
9841
10639
|
generation += 1;
|
|
10640
|
+
map.triggerRepaint();
|
|
9842
10641
|
}
|
|
9843
10642
|
}
|
|
10643
|
+
let fallbackStyle;
|
|
10644
|
+
let fallbackSun;
|
|
10645
|
+
function standaloneStyle() {
|
|
10646
|
+
return fallbackStyle ??= map.getStyle() ?? {};
|
|
10647
|
+
}
|
|
9844
10648
|
let designs;
|
|
9845
10649
|
function designsOnScreen() {
|
|
9846
10650
|
const read = options.controller?.getThemeMetadata?.(tileflowBuildingsMetadataKey) ?? {
|
|
9847
|
-
values: [
|
|
9848
|
-
map.getStyle()?.metadata?.[tileflowBuildingsMetadataKey]
|
|
9849
|
-
],
|
|
10651
|
+
values: [standaloneStyle().metadata?.[tileflowBuildingsMetadataKey]],
|
|
9850
10652
|
position: 0
|
|
9851
10653
|
};
|
|
9852
10654
|
if (!designs || designs.values.length !== read.values.length || designs.values.some((value, index) => value !== read.values[index])) {
|
|
@@ -9970,14 +10772,17 @@ function createTileflowBuildings3D(options) {
|
|
|
9970
10772
|
return [...rgbOf2(typeof text2 === "string" ? text2 : "#d8d0c0") ?? [0.85, 0.82, 0.76]];
|
|
9971
10773
|
}
|
|
9972
10774
|
function sunNow() {
|
|
9973
|
-
return options.controller?.getSun?.() ?? resolveTileflowSun(
|
|
10775
|
+
return options.controller?.getSun?.() ?? (fallbackSun ??= resolveTileflowSun(tileflowStyleSunPreset(standaloneStyle())));
|
|
9974
10776
|
}
|
|
9975
10777
|
function lighting() {
|
|
9976
|
-
|
|
10778
|
+
const light = tileflowSceneLight(sunNow(), design().shadows);
|
|
10779
|
+
return { ...light, strength: light.strength * visibleOpacity() };
|
|
9977
10780
|
}
|
|
9978
10781
|
function visibleScale() {
|
|
9979
|
-
|
|
9980
|
-
|
|
10782
|
+
return visibleOpacity() > 0 ? scale : 0;
|
|
10783
|
+
}
|
|
10784
|
+
function visibleOpacity() {
|
|
10785
|
+
return opacity * tileflowSceneOpacity(map.getZoom(), minzoom);
|
|
9981
10786
|
}
|
|
9982
10787
|
function drawn() {
|
|
9983
10788
|
return camera.ready().filter((entry) => entry.value.gl);
|
|
@@ -10013,19 +10818,45 @@ function createTileflowBuildings3D(options) {
|
|
|
10013
10818
|
const program = programs.depth;
|
|
10014
10819
|
if (!program) return;
|
|
10015
10820
|
context.useProgram(program.program);
|
|
10016
|
-
context.uniform1f(program.uniforms.u_preview_opacity ?? null, opacity);
|
|
10017
10821
|
context.uniform1f(program.uniforms.u_scale, visibleScale());
|
|
10018
10822
|
context.uniform1f(program.uniforms.u_metres, frame.metres);
|
|
10019
10823
|
context.uniformMatrix4fv(program.uniforms.u_light_matrix, false, frame.light.matrix);
|
|
10020
10824
|
for (const { x: x3, y: y2, value } of drawn()) {
|
|
10021
|
-
|
|
10825
|
+
const offset = frame.offsetOf({ x: x3, y: y2 }, camera.zoom);
|
|
10826
|
+
if (!buildingBoxVisible(frame.light.matrix, [
|
|
10827
|
+
(offset[0] - 1) * frame.metres,
|
|
10828
|
+
-(offset[1] + tileflowBuildingExtent + 1) * frame.metres,
|
|
10829
|
+
0,
|
|
10830
|
+
(offset[0] + tileflowBuildingExtent + 1) * frame.metres,
|
|
10831
|
+
-(offset[1] - 1) * frame.metres,
|
|
10832
|
+
value.mesh.top + 0.1
|
|
10833
|
+
]))
|
|
10834
|
+
continue;
|
|
10835
|
+
const ranges = buildingDrawRanges(
|
|
10836
|
+
value.mesh,
|
|
10837
|
+
({ bounds: [left, top, right, bottom, height] }) => buildingBoxVisible(frame.light.matrix, [
|
|
10838
|
+
(left + offset[0]) * frame.metres,
|
|
10839
|
+
-(bottom + offset[1]) * frame.metres,
|
|
10840
|
+
0,
|
|
10841
|
+
(right + offset[0]) * frame.metres,
|
|
10842
|
+
-(top + offset[1]) * frame.metres,
|
|
10843
|
+
height
|
|
10844
|
+
])
|
|
10845
|
+
);
|
|
10846
|
+
if (!ranges.length) continue;
|
|
10847
|
+
context.uniform2f(program.uniforms.u_offset, ...offset);
|
|
10022
10848
|
context.bindVertexArray(value.gl.vao);
|
|
10023
|
-
|
|
10849
|
+
for (const range of ranges)
|
|
10850
|
+
context.drawElements(
|
|
10851
|
+
context.TRIANGLES,
|
|
10852
|
+
range.count,
|
|
10853
|
+
context.UNSIGNED_INT,
|
|
10854
|
+
range.from * 4
|
|
10855
|
+
);
|
|
10024
10856
|
}
|
|
10025
10857
|
const modelDepth = programs.modelDepth;
|
|
10026
10858
|
if (!modelDepth) return;
|
|
10027
10859
|
context.useProgram(modelDepth.program);
|
|
10028
|
-
context.uniform1f(modelDepth.uniforms.u_preview_opacity ?? null, opacity);
|
|
10029
10860
|
context.uniformMatrix4fv(modelDepth.uniforms.u_light_matrix, false, frame.light.matrix);
|
|
10030
10861
|
context.uniform1f(modelDepth.uniforms.u_scale, visibleScale());
|
|
10031
10862
|
context.uniform1f(modelDepth.uniforms.u_metres, frame.metres);
|
|
@@ -10102,6 +10933,46 @@ function createTileflowBuildings3D(options) {
|
|
|
10102
10933
|
context.uniform1f(uniforms.u_projection_transition, data.projectionTransition);
|
|
10103
10934
|
context.uniformMatrix4fv(uniforms.u_projection_fallback_matrix, false, data.fallbackMatrix);
|
|
10104
10935
|
}
|
|
10936
|
+
function viewRanges(args, tile, margin = 0, wallsOnly = false) {
|
|
10937
|
+
const data = args.getProjectionData({
|
|
10938
|
+
tileID: { canonical: { x: tile.x, y: tile.y, z: camera.zoom }, wrap: 0 }
|
|
10939
|
+
});
|
|
10940
|
+
const mercator = args.shaderData.variantName === "mercator";
|
|
10941
|
+
const reach = margin / camera.metres + 1;
|
|
10942
|
+
if (mercator && !buildingBoxVisible(data.mainMatrix, [
|
|
10943
|
+
-reach,
|
|
10944
|
+
-reach,
|
|
10945
|
+
0,
|
|
10946
|
+
tileflowBuildingExtent + reach,
|
|
10947
|
+
tileflowBuildingExtent + reach,
|
|
10948
|
+
tile.value.mesh.top + 0.1
|
|
10949
|
+
]))
|
|
10950
|
+
return [];
|
|
10951
|
+
return buildingDrawRanges(
|
|
10952
|
+
tile.value.mesh,
|
|
10953
|
+
({ bounds: [left, top, right, bottom, height] }) => !mercator || buildingBoxVisible(data.mainMatrix, [
|
|
10954
|
+
left - reach,
|
|
10955
|
+
top - reach,
|
|
10956
|
+
0,
|
|
10957
|
+
right + reach,
|
|
10958
|
+
bottom + reach,
|
|
10959
|
+
height
|
|
10960
|
+
]),
|
|
10961
|
+
wallsOnly
|
|
10962
|
+
);
|
|
10963
|
+
}
|
|
10964
|
+
function groundVisible(args, x3, y2) {
|
|
10965
|
+
if (args.shaderData.variantName !== "mercator") return true;
|
|
10966
|
+
const data = args.getProjectionData({ tileID: { canonical: { x: x3, y: y2, z: camera.zoom }, wrap: 0 } });
|
|
10967
|
+
return buildingBoxVisible(data.mainMatrix, [
|
|
10968
|
+
0,
|
|
10969
|
+
0,
|
|
10970
|
+
0,
|
|
10971
|
+
tileflowBuildingExtent,
|
|
10972
|
+
tileflowBuildingExtent,
|
|
10973
|
+
0
|
|
10974
|
+
]);
|
|
10975
|
+
}
|
|
10105
10976
|
function pixelRatio2(context) {
|
|
10106
10977
|
const canvas = context.canvas;
|
|
10107
10978
|
const width = canvas.clientWidth;
|
|
@@ -10117,90 +10988,105 @@ function createTileflowBuildings3D(options) {
|
|
|
10117
10988
|
const nightLight2 = mixed2((entry) => entry.nightLight);
|
|
10118
10989
|
const edges = mixed2((entry) => entry.edges);
|
|
10119
10990
|
const { occlusion: occlusionOn, windows: windowsOn } = design();
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
context.
|
|
10123
|
-
context.
|
|
10124
|
-
context
|
|
10125
|
-
|
|
10126
|
-
|
|
10127
|
-
|
|
10128
|
-
|
|
10129
|
-
|
|
10130
|
-
|
|
10131
|
-
|
|
10132
|
-
|
|
10133
|
-
|
|
10134
|
-
|
|
10135
|
-
|
|
10136
|
-
|
|
10137
|
-
|
|
10138
|
-
u.
|
|
10139
|
-
|
|
10140
|
-
|
|
10141
|
-
|
|
10142
|
-
|
|
10143
|
-
|
|
10144
|
-
|
|
10145
|
-
|
|
10146
|
-
|
|
10147
|
-
|
|
10148
|
-
|
|
10149
|
-
|
|
10150
|
-
|
|
10151
|
-
|
|
10152
|
-
|
|
10153
|
-
u.
|
|
10154
|
-
|
|
10155
|
-
|
|
10156
|
-
|
|
10157
|
-
|
|
10158
|
-
|
|
10159
|
-
|
|
10160
|
-
|
|
10161
|
-
|
|
10162
|
-
|
|
10163
|
-
|
|
10164
|
-
|
|
10165
|
-
context.
|
|
10166
|
-
|
|
10167
|
-
context.uniform1f(u.
|
|
10168
|
-
|
|
10169
|
-
|
|
10170
|
-
|
|
10171
|
-
|
|
10172
|
-
|
|
10173
|
-
context
|
|
10174
|
-
context.
|
|
10175
|
-
|
|
10176
|
-
|
|
10177
|
-
|
|
10178
|
-
|
|
10179
|
-
|
|
10180
|
-
|
|
10181
|
-
|
|
10182
|
-
|
|
10183
|
-
|
|
10184
|
-
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10189
|
-
|
|
10190
|
-
|
|
10191
|
-
|
|
10192
|
-
|
|
10193
|
-
const centre = map.getCenter();
|
|
10194
|
-
for (const entry of standing) {
|
|
10195
|
-
const stand = standOf(entry);
|
|
10196
|
-
project(context, model, args, stand.tileX, stand.tileY);
|
|
10197
|
-
const at = placed2(stand.tileX, stand.tileY);
|
|
10198
|
-
context.uniform2f(m.u_offset, ...at.offset);
|
|
10199
|
-
context.uniform1f(m.u_metres, at.metres);
|
|
10200
|
-
setPlacement(context, model, entry, stand);
|
|
10201
|
-
drawModel(context, model, entry, models.levelOf(entry, centre));
|
|
10991
|
+
context.enable(context.DEPTH_TEST);
|
|
10992
|
+
context.depthFunc(context.LEQUAL);
|
|
10993
|
+
context.depthMask(true);
|
|
10994
|
+
context.disable(context.BLEND);
|
|
10995
|
+
drawBuildingOpacity(context, visibleOpacity(), () => {
|
|
10996
|
+
const buildings = programs.buildings;
|
|
10997
|
+
const u = buildings.uniforms;
|
|
10998
|
+
context.useProgram(buildings.program);
|
|
10999
|
+
context.uniform1f(u.u_scale, shown);
|
|
11000
|
+
context.uniform1f(u.u_metres, placed2(0, 0).metres);
|
|
11001
|
+
context.uniform3fv(u.u_colour, themeColour());
|
|
11002
|
+
context.uniform3fv(u.u_sun_dir, [...light.direction]);
|
|
11003
|
+
context.uniform3fv(u.u_shadow_dir, light.shadowDirection);
|
|
11004
|
+
context.uniform3fv(u.u_sun_light, light.sunLight);
|
|
11005
|
+
context.uniform3fv(u.u_sky_light, light.skyLight);
|
|
11006
|
+
context.uniform1f(u.u_occlusion, occlusionOn ? tileflowBuildingOcclusion.wall : 0);
|
|
11007
|
+
context.uniform1f(u.u_occlusion_radius, tileflowBuildingOcclusion.wallRadius);
|
|
11008
|
+
context.uniform1f(u.u_corner, tileflowBuildingOcclusion.corner);
|
|
11009
|
+
context.uniform1f(u.u_night, light.night);
|
|
11010
|
+
context.uniform1f(u.u_windows, windowsOn ? 1 : 0);
|
|
11011
|
+
context.uniform1f(u.u_flood, light.night * nightLight2);
|
|
11012
|
+
context.uniform3fv(u.u_flood_colour, flood.colour);
|
|
11013
|
+
context.uniform1f(
|
|
11014
|
+
u.u_variation,
|
|
11015
|
+
mixed2((entry) => entry.colorVariation)
|
|
11016
|
+
);
|
|
11017
|
+
const styleLight = tileflowBuildingStyleLight(
|
|
11018
|
+
map.getLight?.() ?? fallbackStyleLight,
|
|
11019
|
+
map.getBearing()
|
|
11020
|
+
);
|
|
11021
|
+
context.uniform3fv(u.u_style_light_position, styleLight.position);
|
|
11022
|
+
context.uniform3fv(u.u_style_light_colour, styleLight.colour);
|
|
11023
|
+
context.uniform1f(u.u_style_light_intensity, styleLight.intensity);
|
|
11024
|
+
context.uniform1f(u.u_edge_radius, design().edgeRadius ?? 0);
|
|
11025
|
+
context.uniform1f(
|
|
11026
|
+
u.u_style_gradient,
|
|
11027
|
+
mixed2((entry) => entry.verticalGradient ? 1 : 0)
|
|
11028
|
+
);
|
|
11029
|
+
context.uniform1f(
|
|
11030
|
+
u.u_sun_lighting,
|
|
11031
|
+
mixed2((entry) => entry.lighting === "sun" ? 1 : 0)
|
|
11032
|
+
);
|
|
11033
|
+
const edgeColour = edgeColourNow();
|
|
11034
|
+
const lightEdge = edges * (edgeLine.day + (1 - edgeLine.day) * light.night);
|
|
11035
|
+
context.uniform1f(u.u_edge, lightEdge + (edges - lightEdge) * edgeColour.share);
|
|
11036
|
+
context.uniform3fv(u.u_edge_colour, edgeColour.rgb);
|
|
11037
|
+
context.uniform1f(u.u_edge_own, edgeColour.share);
|
|
11038
|
+
context.uniform1f(u.u_edge_width, edgeLine.width * pixelRatio2(context));
|
|
11039
|
+
scene.bindShadow(context, u, light.strength);
|
|
11040
|
+
for (const tile of entries) {
|
|
11041
|
+
const { x: x3, y: y2, value: entry } = tile;
|
|
11042
|
+
const ranges = viewRanges(args, tile);
|
|
11043
|
+
if (!ranges.length) continue;
|
|
11044
|
+
project(context, buildings, args, x3, y2);
|
|
11045
|
+
context.uniform2f(u.u_offset, ...placed2(x3, y2).offset);
|
|
11046
|
+
const palette = themes ? paletteOf(context, entry, themes.themes) : void 0;
|
|
11047
|
+
context.uniform1f(u.u_palette_on, palette ? 1 : 0);
|
|
11048
|
+
context.activeTexture(context.TEXTURE1);
|
|
11049
|
+
context.bindTexture(context.TEXTURE_2D, palette?.texture ?? blank ?? null);
|
|
11050
|
+
context.uniform1i(u.u_palette, 1);
|
|
11051
|
+
if (palette && themes) {
|
|
11052
|
+
context.uniform2f(u.u_palette_rows, palette.rows, paletteWidth);
|
|
11053
|
+
context.uniform2f(u.u_palette_themes, themes.lower, themes.upper);
|
|
11054
|
+
context.uniform1f(u.u_palette_mix, themes.mix);
|
|
11055
|
+
}
|
|
11056
|
+
context.bindVertexArray(entry.gl.vao);
|
|
11057
|
+
for (const range of ranges)
|
|
11058
|
+
context.drawElements(
|
|
11059
|
+
context.TRIANGLES,
|
|
11060
|
+
range.count,
|
|
11061
|
+
context.UNSIGNED_INT,
|
|
11062
|
+
range.from * 4
|
|
11063
|
+
);
|
|
10202
11064
|
}
|
|
10203
|
-
|
|
11065
|
+
const model = programs.model;
|
|
11066
|
+
if (model && standing.length > 0) {
|
|
11067
|
+
const m = model.uniforms;
|
|
11068
|
+
context.useProgram(model.program);
|
|
11069
|
+
context.disable(context.CULL_FACE);
|
|
11070
|
+
context.uniform1f(m.u_scale, shown);
|
|
11071
|
+
context.uniform3fv(m.u_sun_dir, [...light.direction]);
|
|
11072
|
+
context.uniform3fv(m.u_shadow_dir, light.shadowDirection);
|
|
11073
|
+
context.uniform3fv(m.u_sun_light, light.sunLight);
|
|
11074
|
+
context.uniform3fv(m.u_sky_light, light.skyLight);
|
|
11075
|
+
context.uniform1f(m.u_night, light.night);
|
|
11076
|
+
context.uniform3fv(m.u_part_colours, partColoursNow());
|
|
11077
|
+
scene.bindShadow(context, m, light.strength);
|
|
11078
|
+
const centre = map.getCenter();
|
|
11079
|
+
for (const entry of standing) {
|
|
11080
|
+
const stand = standOf(entry);
|
|
11081
|
+
project(context, model, args, stand.tileX, stand.tileY);
|
|
11082
|
+
const at = placed2(stand.tileX, stand.tileY);
|
|
11083
|
+
context.uniform2f(m.u_offset, ...at.offset);
|
|
11084
|
+
context.uniform1f(m.u_metres, at.metres);
|
|
11085
|
+
setPlacement(context, model, entry, stand);
|
|
11086
|
+
drawModel(context, model, entry, models.levelOf(entry, centre));
|
|
11087
|
+
}
|
|
11088
|
+
}
|
|
11089
|
+
});
|
|
10204
11090
|
context.depthMask(false);
|
|
10205
11091
|
context.enable(context.BLEND);
|
|
10206
11092
|
context.blendFuncSeparate(context.DST_COLOR, context.ZERO, context.ZERO, context.ONE);
|
|
@@ -10208,7 +11094,6 @@ function createTileflowBuildings3D(options) {
|
|
|
10208
11094
|
if (light.strength > 0 && frame && frame.zoom === camera.zoom && ground && scene.drawsGround(caster)) {
|
|
10209
11095
|
const program = programs.ground;
|
|
10210
11096
|
context.useProgram(program.program);
|
|
10211
|
-
context.uniform1f(program.uniforms.u_preview_opacity ?? null, opacity);
|
|
10212
11097
|
context.uniform1f(program.uniforms.u_metres, frame.metres);
|
|
10213
11098
|
context.uniform3fv(program.uniforms.u_ground_shadow, light.groundShadow);
|
|
10214
11099
|
scene.bindShadow(context, program.uniforms, light.strength);
|
|
@@ -10223,45 +11108,80 @@ function createTileflowBuildings3D(options) {
|
|
|
10223
11108
|
const [left, top, right, bottom] = frame.tiles;
|
|
10224
11109
|
for (let x3 = left - margin; x3 <= right + margin; x3 += 1)
|
|
10225
11110
|
for (let y2 = top - margin; y2 <= bottom + margin; y2 += 1) {
|
|
11111
|
+
if (!groundVisible(args, x3, y2)) continue;
|
|
10226
11112
|
project(context, program, args, x3, y2);
|
|
10227
11113
|
context.uniform2f(program.uniforms.u_offset, ...frame.offsetOf({ x: x3, y: y2 }));
|
|
10228
11114
|
context.drawArrays(context.TRIANGLE_STRIP, 0, 4);
|
|
10229
11115
|
}
|
|
10230
11116
|
}
|
|
10231
11117
|
const walls = (program) => {
|
|
10232
|
-
for (const
|
|
11118
|
+
for (const tile of entries) {
|
|
11119
|
+
const { x: x3, y: y2, value } = tile;
|
|
10233
11120
|
const mesh = value.mesh;
|
|
10234
11121
|
if (mesh.indices.length <= mesh.wallsFrom) continue;
|
|
10235
11122
|
project(context, program, args, x3, y2);
|
|
10236
11123
|
context.uniform2f(program.uniforms.u_offset, ...camera.offsetOf({ x: x3, y: y2 }));
|
|
10237
11124
|
context.bindVertexArray(value.gl.vao);
|
|
10238
|
-
|
|
10239
|
-
context.
|
|
10240
|
-
|
|
10241
|
-
|
|
10242
|
-
|
|
10243
|
-
|
|
11125
|
+
for (const range of viewRanges(args, tile, flood.reach, true))
|
|
11126
|
+
context.drawElements(
|
|
11127
|
+
context.TRIANGLES,
|
|
11128
|
+
range.count,
|
|
11129
|
+
context.UNSIGNED_INT,
|
|
11130
|
+
range.from * 4
|
|
11131
|
+
);
|
|
10244
11132
|
}
|
|
10245
11133
|
};
|
|
10246
|
-
if (occlusionOn) {
|
|
10247
|
-
const program = programs.
|
|
10248
|
-
|
|
10249
|
-
|
|
10250
|
-
|
|
10251
|
-
|
|
10252
|
-
|
|
10253
|
-
|
|
10254
|
-
|
|
11134
|
+
if (occlusionOn && entries.length > 0 && ground) {
|
|
11135
|
+
const program = programs.contact;
|
|
11136
|
+
const texture = contactMask.render(context, () => {
|
|
11137
|
+
context.useProgram(program.program);
|
|
11138
|
+
context.uniform1f(program.uniforms.u_scale, shown);
|
|
11139
|
+
context.uniform1f(program.uniforms.u_metres, camera.metres);
|
|
11140
|
+
context.uniform1f(program.uniforms.u_band, tileflowBuildingOcclusion.ground);
|
|
11141
|
+
context.uniform1f(
|
|
11142
|
+
program.uniforms.u_band_strength,
|
|
11143
|
+
tileflowBuildingOcclusion.groundStrength * visibleOpacity()
|
|
11144
|
+
);
|
|
11145
|
+
for (const tile of entries) {
|
|
11146
|
+
const { x: x3, y: y2, value } = tile;
|
|
11147
|
+
const ranges = viewRanges(args, tile, tileflowBuildingOcclusion.ground, true);
|
|
11148
|
+
if (!ranges.length) continue;
|
|
11149
|
+
const buffers = value.gl;
|
|
11150
|
+
buffers.contact ??= createBuildingContactVao(context, value.mesh);
|
|
11151
|
+
project(context, program, args, x3, y2);
|
|
11152
|
+
context.bindVertexArray(buffers.contact.vao);
|
|
11153
|
+
for (const range of ranges) {
|
|
11154
|
+
buffers.contact.offset((range.from - value.mesh.wallsFrom) / 6);
|
|
11155
|
+
context.drawArraysInstanced(context.TRIANGLE_STRIP, 0, 4, range.count / 6);
|
|
11156
|
+
}
|
|
11157
|
+
}
|
|
11158
|
+
});
|
|
11159
|
+
const composite = programs.contactComposite;
|
|
11160
|
+
context.useProgram(composite.program);
|
|
11161
|
+
context.activeTexture(context.TEXTURE2);
|
|
11162
|
+
context.bindTexture(context.TEXTURE_2D, texture);
|
|
11163
|
+
context.uniform1i(composite.uniforms.u_contact, 2);
|
|
11164
|
+
context.bindVertexArray(ground.vao);
|
|
11165
|
+
const xs = entries.map(({ x: x3 }) => x3);
|
|
11166
|
+
const ys = entries.map(({ y: y2 }) => y2);
|
|
11167
|
+
for (let x3 = Math.min(...xs) - 1; x3 <= Math.max(...xs) + 1; x3 += 1)
|
|
11168
|
+
for (let y2 = Math.min(...ys) - 1; y2 <= Math.max(...ys) + 1; y2 += 1) {
|
|
11169
|
+
if (!groundVisible(args, x3, y2)) continue;
|
|
11170
|
+
project(context, composite, args, x3, y2);
|
|
11171
|
+
context.drawArrays(context.TRIANGLE_STRIP, 0, 4);
|
|
11172
|
+
}
|
|
11173
|
+
context.bindTexture(context.TEXTURE_2D, null);
|
|
11174
|
+
} else {
|
|
11175
|
+
contactMask.release(context);
|
|
10255
11176
|
}
|
|
10256
11177
|
if (nightLight2 > 0 && light.night > 0.01) {
|
|
10257
11178
|
const program = programs.flood;
|
|
10258
11179
|
context.useProgram(program.program);
|
|
10259
|
-
context.uniform1f(program.uniforms.u_preview_opacity ?? null, opacity);
|
|
10260
11180
|
context.blendFuncSeparate(context.ONE, context.ONE, context.ZERO, context.ONE);
|
|
10261
11181
|
context.uniform1f(program.uniforms.u_scale, shown);
|
|
10262
11182
|
context.uniform1f(program.uniforms.u_metres, camera.metres);
|
|
10263
11183
|
context.uniform1f(program.uniforms.u_band, flood.reach);
|
|
10264
|
-
context.uniform1f(program.uniforms.u_flood, light.night * nightLight2);
|
|
11184
|
+
context.uniform1f(program.uniforms.u_flood, light.night * nightLight2 * visibleOpacity());
|
|
10265
11185
|
context.uniform3fv(program.uniforms.u_flood_colour, flood.colour);
|
|
10266
11186
|
walls(program);
|
|
10267
11187
|
}
|
|
@@ -10275,7 +11195,8 @@ ${shaderData.define}`;
|
|
|
10275
11195
|
const sources = {
|
|
10276
11196
|
buildings: [tileflowBuildingVertex(projected), tileflowBuildingFragment],
|
|
10277
11197
|
ground: [tileflowBuildingGroundVertex(projected), tileflowBuildingGroundFragment],
|
|
10278
|
-
|
|
11198
|
+
contact: [buildingContactVertex(projected), buildingContactFragment],
|
|
11199
|
+
contactComposite: [tileflowBuildingGroundVertex(projected), buildingContactCompositeFragment],
|
|
10279
11200
|
flood: [tileflowBuildingBandVertex(projected), tileflowBuildingFloodFragment]
|
|
10280
11201
|
};
|
|
10281
11202
|
sources.model = [tileflowModelVertex(projected), tileflowModelFragment];
|
|
@@ -10292,11 +11213,7 @@ ${shaderData.define}`;
|
|
|
10292
11213
|
};
|
|
10293
11214
|
const program = context.createProgram();
|
|
10294
11215
|
const vertex = shader(context.VERTEX_SHADER, vertexText);
|
|
10295
|
-
const
|
|
10296
|
-
"void main() {",
|
|
10297
|
-
"uniform float u_preview_opacity;\nvoid main() {\n if (u_preview_opacity < 1.0 && fract(sin(dot(floor(gl_FragCoord.xy), vec2(12.9898, 78.233))) * 43758.5453) >= u_preview_opacity) discard;"
|
|
10298
|
-
);
|
|
10299
|
-
const fragment = shader(context.FRAGMENT_SHADER, fadeFragment);
|
|
11216
|
+
const fragment = shader(context.FRAGMENT_SHADER, fragmentText);
|
|
10300
11217
|
context.attachShader(program, vertex);
|
|
10301
11218
|
context.attachShader(program, fragment);
|
|
10302
11219
|
const bound = name === "model" || name === "modelDepth" ? tileflowModelAttributes : tileflowBuildingAttributes;
|
|
@@ -10380,13 +11297,15 @@ ${shaderData.define}`;
|
|
|
10380
11297
|
}
|
|
10381
11298
|
let fallbackStyleLight;
|
|
10382
11299
|
const onStyle = () => {
|
|
10383
|
-
|
|
10384
|
-
|
|
11300
|
+
fallbackStyle = void 0;
|
|
11301
|
+
fallbackSun = void 0;
|
|
11302
|
+
if (!map.getLight) fallbackStyleLight = standaloneStyle().light;
|
|
10385
11303
|
attach();
|
|
10386
11304
|
};
|
|
10387
11305
|
map.on("styledata", onStyle);
|
|
10388
11306
|
map.on("idle", onStyle);
|
|
10389
11307
|
onStyle();
|
|
11308
|
+
camera.update();
|
|
10390
11309
|
function grow(goal, change) {
|
|
10391
11310
|
if (disposed) return Promise.resolve();
|
|
10392
11311
|
if (!Number.isFinite(goal))
|
|
@@ -10508,6 +11427,7 @@ ${shaderData.define}`;
|
|
|
10508
11427
|
} catch {
|
|
10509
11428
|
}
|
|
10510
11429
|
camera.dispose();
|
|
11430
|
+
mesher.dispose();
|
|
10511
11431
|
}
|
|
10512
11432
|
};
|
|
10513
11433
|
}
|
|
@@ -10831,11 +11751,12 @@ function createTileflowStreetLights(options) {
|
|
|
10831
11751
|
let corners;
|
|
10832
11752
|
let disposed = false;
|
|
10833
11753
|
let lastUpdate = 0;
|
|
11754
|
+
let fallbackMetadata;
|
|
10834
11755
|
let designs;
|
|
10835
11756
|
function designsOnScreen() {
|
|
10836
11757
|
const read = options.controller?.getThemeMetadata?.(tileflowStreetLightsMetadataKey) ?? {
|
|
10837
11758
|
values: [
|
|
10838
|
-
map.getStyle()?.metadata
|
|
11759
|
+
(fallbackMetadata ??= map.getStyle()?.metadata ?? {})[tileflowStreetLightsMetadataKey]
|
|
10839
11760
|
],
|
|
10840
11761
|
position: 0
|
|
10841
11762
|
};
|
|
@@ -10845,7 +11766,9 @@ function createTileflowStreetLights(options) {
|
|
|
10845
11766
|
);
|
|
10846
11767
|
designs = {
|
|
10847
11768
|
values: read.values,
|
|
10848
|
-
parsed: parsed.map(
|
|
11769
|
+
parsed: parsed.map(
|
|
11770
|
+
(entry) => options.enabled === false ? disabledStreetLightsDesign : entry ?? (options.enabled === true ? tileflowDefaultStreetLightsDesign : disabledStreetLightsDesign)
|
|
11771
|
+
),
|
|
10849
11772
|
declared: parsed.some((entry) => entry !== void 0)
|
|
10850
11773
|
};
|
|
10851
11774
|
}
|
|
@@ -10929,6 +11852,7 @@ function createTileflowStreetLights(options) {
|
|
|
10929
11852
|
return options.controller?.getSun?.() ?? resolveTileflowSun(tileflowDefaultSunPreset);
|
|
10930
11853
|
}
|
|
10931
11854
|
function onNow() {
|
|
11855
|
+
if (appearance().intensity <= 0) return 0;
|
|
10932
11856
|
const brightness = sunNow().brightness;
|
|
10933
11857
|
return Math.min(1, Math.max(0, (switchOn[1] - brightness) / (switchOn[1] - switchOn[0])));
|
|
10934
11858
|
}
|
|
@@ -11106,13 +12030,22 @@ ${shaderData.define}`;
|
|
|
11106
12030
|
return style?.layers?.find((entry) => entry.type === "symbol")?.id;
|
|
11107
12031
|
}
|
|
11108
12032
|
function attach() {
|
|
11109
|
-
if (disposed ||
|
|
12033
|
+
if (disposed || !map.isStyleLoaded()) return;
|
|
12034
|
+
const enabled = designsOnScreen().themes.some((theme) => theme.intensity > 0);
|
|
12035
|
+
if (!enabled) {
|
|
12036
|
+
if (map.getLayer(tileflowStreetLightsLayerId)) map.removeLayer(tileflowStreetLightsLayerId);
|
|
12037
|
+
return;
|
|
12038
|
+
}
|
|
12039
|
+
if (map.getLayer(tileflowStreetLightsLayerId)) return;
|
|
11110
12040
|
try {
|
|
11111
12041
|
map.addLayer(layer, beforeId());
|
|
11112
12042
|
} catch {
|
|
11113
12043
|
}
|
|
11114
12044
|
}
|
|
11115
|
-
const onStyle = () =>
|
|
12045
|
+
const onStyle = () => {
|
|
12046
|
+
fallbackMetadata = void 0;
|
|
12047
|
+
attach();
|
|
12048
|
+
};
|
|
11116
12049
|
map.on("styledata", onStyle);
|
|
11117
12050
|
map.on("idle", onStyle);
|
|
11118
12051
|
attach();
|
|
@@ -11148,6 +12081,10 @@ ${shaderData.define}`;
|
|
|
11148
12081
|
}
|
|
11149
12082
|
};
|
|
11150
12083
|
}
|
|
12084
|
+
var disabledStreetLightsDesign = Object.freeze({
|
|
12085
|
+
...tileflowDefaultStreetLightsDesign,
|
|
12086
|
+
intensity: 0
|
|
12087
|
+
});
|
|
11151
12088
|
var emptyTile2 = Object.freeze({
|
|
11152
12089
|
placement: Object.freeze({ lamps: new Uint8Array(0), mapped: 0, estimated: 0 })
|
|
11153
12090
|
});
|
|
@@ -11265,7 +12202,9 @@ var tileflowWorldTreeFields = Object.freeze({
|
|
|
11265
12202
|
canopyRank: "rank",
|
|
11266
12203
|
canopyClearance: "clearance_px_z15",
|
|
11267
12204
|
canopyCover: "cover_pct",
|
|
11268
|
-
canopyHeight: "height_m"
|
|
12205
|
+
canopyHeight: "height_m",
|
|
12206
|
+
subclass: "subclass",
|
|
12207
|
+
hedge: "hedge"
|
|
11269
12208
|
});
|
|
11270
12209
|
var tileflowDefaultUnknownTreeForms = Object.freeze({
|
|
11271
12210
|
round: 1,
|
|
@@ -11289,6 +12228,11 @@ function tileflowTreeFormProportion(form) {
|
|
|
11289
12228
|
}
|
|
11290
12229
|
var heightBounds = [2, 45];
|
|
11291
12230
|
var crownBounds = [1, 25];
|
|
12231
|
+
var hedgeSpacing = 1.2;
|
|
12232
|
+
var hedgeLength = 1.5;
|
|
12233
|
+
var hedgeHeight = 1.3;
|
|
12234
|
+
var hedgeBank = 1;
|
|
12235
|
+
var hedgeBounds = [0.3, 10];
|
|
11292
12236
|
var palms = /* @__PURE__ */ new Set([
|
|
11293
12237
|
"acoelorrhaphe",
|
|
11294
12238
|
"adonidia",
|
|
@@ -11586,6 +12530,49 @@ function placeTileflowTrees(layers, options) {
|
|
|
11586
12530
|
}
|
|
11587
12531
|
}
|
|
11588
12532
|
}
|
|
12533
|
+
const hedges = [];
|
|
12534
|
+
const barriers = layers.hedges;
|
|
12535
|
+
if (barriers) {
|
|
12536
|
+
const scale = extent3 / barriers.extent;
|
|
12537
|
+
const step = hedgeSpacing / (tileMetres / barriers.extent);
|
|
12538
|
+
for (const feature of barriers.features) {
|
|
12539
|
+
if (feature.type === "Point" || feature.properties[fields.subclass] !== "hedge") continue;
|
|
12540
|
+
const bank = feature.properties[fields.hedge] === "hedge_bank" ? hedgeBank : 0;
|
|
12541
|
+
const height = (measured(feature.properties[fields.height], hedgeBounds) ?? hedgeHeight) + bank;
|
|
12542
|
+
for (const points2 of feature.geometry) {
|
|
12543
|
+
let next = step / 2;
|
|
12544
|
+
let along = 0;
|
|
12545
|
+
for (let index = 0; index + 3 < points2.length; index += 2) {
|
|
12546
|
+
const [ax, ay, bx, by] = [
|
|
12547
|
+
points2[index],
|
|
12548
|
+
points2[index + 1],
|
|
12549
|
+
points2[index + 2],
|
|
12550
|
+
points2[index + 3]
|
|
12551
|
+
];
|
|
12552
|
+
const length = Math.hypot(bx - ax, by - ay);
|
|
12553
|
+
const turn = (Math.atan2(ay - by, bx - ax) / (Math.PI * 2) + 1) % 1;
|
|
12554
|
+
while (next <= along + length) {
|
|
12555
|
+
const t = length > 0 ? (next - along) / length : 0;
|
|
12556
|
+
const px = ax + (bx - ax) * t;
|
|
12557
|
+
const py = ay + (by - ay) * t;
|
|
12558
|
+
if (px >= 0 && py >= 0 && px < barriers.extent && py < barriers.extent) {
|
|
12559
|
+
const seed = hash3(tile.x * extent3 + px * scale, tile.y * extent3 + py * scale, 17);
|
|
12560
|
+
hedges.push({
|
|
12561
|
+
x: px * scale,
|
|
12562
|
+
y: py * scale,
|
|
12563
|
+
height,
|
|
12564
|
+
turn,
|
|
12565
|
+
shade: unit(seed, 5),
|
|
12566
|
+
seed: unit(seed, 7)
|
|
12567
|
+
});
|
|
12568
|
+
}
|
|
12569
|
+
next += step;
|
|
12570
|
+
}
|
|
12571
|
+
along += length;
|
|
12572
|
+
}
|
|
12573
|
+
}
|
|
12574
|
+
}
|
|
12575
|
+
}
|
|
11589
12576
|
const variantsOf = (form) => clamp(Math.floor(options.variants?.[tileflowTreeForms[form]] ?? 1), [1, 4]);
|
|
11590
12577
|
const variant = (tree) => Math.min(
|
|
11591
12578
|
variantsOf(tree.form) - 1,
|
|
@@ -11630,7 +12617,20 @@ function placeTileflowTrees(layers, options) {
|
|
|
11630
12617
|
count(ranges, index, tree.rank);
|
|
11631
12618
|
count(ranges.variants[variants[from]], index, tree.rank);
|
|
11632
12619
|
});
|
|
11633
|
-
|
|
12620
|
+
const hedgeBytes = new Uint8Array(hedges.length * tileflowTreeBytes);
|
|
12621
|
+
const hedgeView = new DataView(hedgeBytes.buffer);
|
|
12622
|
+
hedges.forEach((piece, index) => {
|
|
12623
|
+
const at = index * tileflowTreeBytes;
|
|
12624
|
+
hedgeView.setUint16(at, Math.min(extent3 - 1, Math.round(piece.x)), true);
|
|
12625
|
+
hedgeView.setUint16(at + 2, Math.min(extent3 - 1, Math.round(piece.y)), true);
|
|
12626
|
+
hedgeView.setUint16(at + 4, Math.round(piece.height * 10), true);
|
|
12627
|
+
hedgeView.setUint16(at + 6, Math.round(hedgeLength * 10), true);
|
|
12628
|
+
hedgeBytes[at + 8] = Math.round(piece.turn * 256) & 255;
|
|
12629
|
+
hedgeBytes[at + 9] = Math.floor(piece.shade * 256) & 255;
|
|
12630
|
+
hedgeBytes[at + 12] = Math.floor(piece.seed * 256) & 255;
|
|
12631
|
+
tallest = Math.max(tallest, piece.height);
|
|
12632
|
+
});
|
|
12633
|
+
return { trees: bytes, forms, hedges: hedgeBytes, tallest, mapped, canopy };
|
|
11634
12634
|
}
|
|
11635
12635
|
function broadleafOf(mix2) {
|
|
11636
12636
|
const broad = { round: mix2.round ?? 0, open: mix2.open ?? 0, tall: mix2.tall ?? 0 };
|
|
@@ -11688,6 +12688,7 @@ function readTileflowVegetation3D(metadata) {
|
|
|
11688
12688
|
const conifer = palette(colors.conifer);
|
|
11689
12689
|
const palm = palette(colors.palm) ?? broadleaf;
|
|
11690
12690
|
if (!broadleaf || !conifer || !palm) return void 0;
|
|
12691
|
+
const hedge = palette(colors.hedge) ?? broadleaf.slice(0, 2);
|
|
11691
12692
|
const scale = (number) => typeof number === "number" && number > 0 && number <= 10 ? number : 1;
|
|
11692
12693
|
const mix2 = tileflowTreeFormMixProblem(unknownForms) === void 0 ? unknownForms : void 0;
|
|
11693
12694
|
const models = Object.fromEntries(
|
|
@@ -11731,10 +12732,11 @@ function readTileflowVegetation3D(metadata) {
|
|
|
11731
12732
|
source,
|
|
11732
12733
|
layers: {
|
|
11733
12734
|
tree: layers.tree,
|
|
11734
|
-
...typeof layers.canopy === "string" ? { canopy: layers.canopy } : {}
|
|
12735
|
+
...typeof layers.canopy === "string" ? { canopy: layers.canopy } : {},
|
|
12736
|
+
...typeof layers.hedge === "string" ? { hedge: layers.hedge } : {}
|
|
11735
12737
|
},
|
|
11736
12738
|
fields: named,
|
|
11737
|
-
colors: { bark: colors.bark, broadleaf, conifer, palm },
|
|
12739
|
+
colors: { bark: colors.bark, broadleaf, conifer, palm, hedge },
|
|
11738
12740
|
heightScale: scale(heightScale),
|
|
11739
12741
|
crownScale: scale(crownScale),
|
|
11740
12742
|
forms,
|
|
@@ -11774,6 +12776,75 @@ function tileflowTreeMeshes() {
|
|
|
11774
12776
|
])
|
|
11775
12777
|
);
|
|
11776
12778
|
}
|
|
12779
|
+
function tileflowHedgeMesh() {
|
|
12780
|
+
const out = {
|
|
12781
|
+
positions: [],
|
|
12782
|
+
normals: [],
|
|
12783
|
+
rounds: [],
|
|
12784
|
+
parts: [],
|
|
12785
|
+
shades: [],
|
|
12786
|
+
occlusion: []
|
|
12787
|
+
};
|
|
12788
|
+
const half = 0.275;
|
|
12789
|
+
const profile = [
|
|
12790
|
+
[-half, 0],
|
|
12791
|
+
[-half, 0.78],
|
|
12792
|
+
[-half * 0.62, 1],
|
|
12793
|
+
[half * 0.62, 1],
|
|
12794
|
+
[half, 0.78],
|
|
12795
|
+
[half, 0]
|
|
12796
|
+
];
|
|
12797
|
+
const round2 = ([y2, z2]) => normalize([0, y2 / half, Math.max(0, z2 - 0.45) / 0.55]);
|
|
12798
|
+
const shaded = (z2) => 0.55 + 0.45 * z2;
|
|
12799
|
+
for (let index = 0; index + 1 < profile.length; index += 1) {
|
|
12800
|
+
const [y0, z0] = profile[index];
|
|
12801
|
+
const [y1, z1] = profile[index + 1];
|
|
12802
|
+
let facet = normalize([0, z1 - z0, -(y1 - y0)]);
|
|
12803
|
+
if (facet[1] * (y0 + y1) + facet[2] * (z0 + z1 - 0.9) < 0)
|
|
12804
|
+
facet = [-facet[0], -facet[1], -facet[2]];
|
|
12805
|
+
const quad = [
|
|
12806
|
+
[-0.5, y0, z0],
|
|
12807
|
+
[0.5, y0, z0],
|
|
12808
|
+
[0.5, y1, z1],
|
|
12809
|
+
[-0.5, y1, z1]
|
|
12810
|
+
];
|
|
12811
|
+
const rounds = [round2([y0, z0]), round2([y0, z0]), round2([y1, z1]), round2([y1, z1])];
|
|
12812
|
+
for (const [a, b2, c] of [
|
|
12813
|
+
[0, 1, 2],
|
|
12814
|
+
[0, 2, 3]
|
|
12815
|
+
])
|
|
12816
|
+
triangle(
|
|
12817
|
+
out,
|
|
12818
|
+
[quad[a], quad[b2], quad[c]],
|
|
12819
|
+
[facet, facet, facet],
|
|
12820
|
+
tileflowTreeParts.leaves,
|
|
12821
|
+
0,
|
|
12822
|
+
[shaded(quad[a][2]), shaded(quad[b2][2]), shaded(quad[c][2])],
|
|
12823
|
+
[rounds[a], rounds[b2], rounds[c]]
|
|
12824
|
+
);
|
|
12825
|
+
}
|
|
12826
|
+
for (const x3 of [-0.5, 0.5]) {
|
|
12827
|
+
const facet = [Math.sign(x3), 0, 0];
|
|
12828
|
+
const centre = [x3, 0, 0.5];
|
|
12829
|
+
for (let index = 0; index + 1 < profile.length; index += 1) {
|
|
12830
|
+
const points = [
|
|
12831
|
+
centre,
|
|
12832
|
+
[x3, ...profile[index]],
|
|
12833
|
+
[x3, ...profile[index + 1]]
|
|
12834
|
+
];
|
|
12835
|
+
triangle(
|
|
12836
|
+
out,
|
|
12837
|
+
points,
|
|
12838
|
+
[facet, facet, facet],
|
|
12839
|
+
tileflowTreeParts.leaves,
|
|
12840
|
+
0,
|
|
12841
|
+
[shaded(0.5), shaded(points[1][2]), shaded(points[2][2])],
|
|
12842
|
+
points.map((point) => normalize([Math.sign(x3), point[1] / half, point[2] - 0.45]))
|
|
12843
|
+
);
|
|
12844
|
+
}
|
|
12845
|
+
}
|
|
12846
|
+
return pack(out);
|
|
12847
|
+
}
|
|
11777
12848
|
function tileflowTreeModelMeshOf(model) {
|
|
11778
12849
|
const out = {
|
|
11779
12850
|
positions: [],
|
|
@@ -11971,12 +13042,12 @@ function lobe(out, centre, radii, seed, shade, crownBase, shape = "icosa") {
|
|
|
11971
13042
|
if (dot2(facet, outward) < 0) facet = [-facet[0], -facet[1], -facet[2]];
|
|
11972
13043
|
const normals = [facet, facet, facet];
|
|
11973
13044
|
const rounds = [a, b2, c].map((index) => round2[index]);
|
|
11974
|
-
const
|
|
13045
|
+
const occlusion = points.map((point) => {
|
|
11975
13046
|
const height = smooth2(crownBase, 1, point[2]);
|
|
11976
13047
|
const outward2 = Math.min(1, Math.hypot(point[0], point[1]) / 0.45);
|
|
11977
13048
|
return 0.5 + 0.32 * height + 0.18 * outward2;
|
|
11978
13049
|
});
|
|
11979
|
-
triangle(out, points, normals, tileflowTreeParts.leaves, shade,
|
|
13050
|
+
triangle(out, points, normals, tileflowTreeParts.leaves, shade, occlusion, rounds);
|
|
11980
13051
|
}
|
|
11981
13052
|
}
|
|
11982
13053
|
function cone(out, z0, z1, radius, sides, shade, closed) {
|
|
@@ -12071,7 +13142,7 @@ function palmTrunk(out, rings, sides) {
|
|
|
12071
13142
|
ringAt.z
|
|
12072
13143
|
];
|
|
12073
13144
|
const normal = (angle) => normalize([Math.cos(angle), Math.sin(angle), 0.05]);
|
|
12074
|
-
const
|
|
13145
|
+
const occlusion = [
|
|
12075
13146
|
0.7 + 0.3 * (a.z / palmTop[2]),
|
|
12076
13147
|
0.7 + 0.3 * (a.z / palmTop[2]),
|
|
12077
13148
|
0.7 + 0.3 * (b2.z / palmTop[2])
|
|
@@ -12082,7 +13153,7 @@ function palmTrunk(out, rings, sides) {
|
|
|
12082
13153
|
[normal(a0), normal(a1), normal(a1)],
|
|
12083
13154
|
tileflowTreeParts.stem,
|
|
12084
13155
|
shade,
|
|
12085
|
-
|
|
13156
|
+
occlusion
|
|
12086
13157
|
);
|
|
12087
13158
|
triangle(
|
|
12088
13159
|
out,
|
|
@@ -12090,7 +13161,7 @@ function palmTrunk(out, rings, sides) {
|
|
|
12090
13161
|
[normal(a0), normal(a1), normal(a0)],
|
|
12091
13162
|
tileflowTreeParts.stem,
|
|
12092
13163
|
shade,
|
|
12093
|
-
[
|
|
13164
|
+
[occlusion[0], occlusion[2], occlusion[2]]
|
|
12094
13165
|
);
|
|
12095
13166
|
}
|
|
12096
13167
|
}
|
|
@@ -12126,7 +13197,7 @@ function fronds(out, count, segments, tier) {
|
|
|
12126
13197
|
];
|
|
12127
13198
|
};
|
|
12128
13199
|
const [a, b2] = [spine[step], spine[step + 1]];
|
|
12129
|
-
const
|
|
13200
|
+
const occlusion = [0.72 + 0.28 * t0, 0.72 + 0.28 * t1, 0.72 + 0.28 * t1];
|
|
12130
13201
|
for (const sign of [-1, 1]) {
|
|
12131
13202
|
const quad = [a, b2, edge(b2, t1, sign), edge(a, t0, sign)];
|
|
12132
13203
|
let facet = faceNormal([quad[0], quad[1], quad[2]]);
|
|
@@ -12138,7 +13209,7 @@ function fronds(out, count, segments, tier) {
|
|
|
12138
13209
|
[normal, normal, normal],
|
|
12139
13210
|
tileflowTreeParts.fronds,
|
|
12140
13211
|
shade,
|
|
12141
|
-
|
|
13212
|
+
occlusion
|
|
12142
13213
|
);
|
|
12143
13214
|
triangle(
|
|
12144
13215
|
out,
|
|
@@ -12146,13 +13217,13 @@ function fronds(out, count, segments, tier) {
|
|
|
12146
13217
|
[normal, normal, normal],
|
|
12147
13218
|
tileflowTreeParts.fronds,
|
|
12148
13219
|
shade,
|
|
12149
|
-
[
|
|
13220
|
+
[occlusion[0], occlusion[2], occlusion[0]]
|
|
12150
13221
|
);
|
|
12151
13222
|
}
|
|
12152
13223
|
}
|
|
12153
13224
|
}
|
|
12154
13225
|
}
|
|
12155
|
-
function triangle(out, points, normals, part, shade,
|
|
13226
|
+
function triangle(out, points, normals, part, shade, occlusion, rounds = normals) {
|
|
12156
13227
|
const facing = dot2(faceNormal(points), [
|
|
12157
13228
|
normals[0][0] + normals[1][0] + normals[2][0],
|
|
12158
13229
|
normals[0][1] + normals[1][1] + normals[2][1],
|
|
@@ -12165,7 +13236,7 @@ function triangle(out, points, normals, part, shade, occlusion2, rounds = normal
|
|
|
12165
13236
|
out.rounds.push(...rounds[index]);
|
|
12166
13237
|
out.parts.push(part);
|
|
12167
13238
|
out.shades.push(shade);
|
|
12168
|
-
out.occlusion.push(Math.min(1, Math.max(0,
|
|
13239
|
+
out.occlusion.push(Math.min(1, Math.max(0, occlusion[index])));
|
|
12169
13240
|
}
|
|
12170
13241
|
}
|
|
12171
13242
|
function pack(out) {
|
|
@@ -12322,7 +13393,7 @@ function decodeTileflowTreeModel(text2, level) {
|
|
|
12322
13393
|
const normals = [];
|
|
12323
13394
|
const parts = [];
|
|
12324
13395
|
const shades = [];
|
|
12325
|
-
const
|
|
13396
|
+
const occlusion = [];
|
|
12326
13397
|
for (let index = 0; index < count; index += 1) {
|
|
12327
13398
|
const at = headerBytes + index * vertexBytes;
|
|
12328
13399
|
for (let axis = 0; axis < 3; axis += 1) {
|
|
@@ -12332,7 +13403,7 @@ function decodeTileflowTreeModel(text2, level) {
|
|
|
12332
13403
|
if (bytes[at + 9] >= partCount2) return void 0;
|
|
12333
13404
|
parts.push(bytes[at + 9]);
|
|
12334
13405
|
shades.push(bytes[at + 10] / 255 * 2 - 1);
|
|
12335
|
-
|
|
13406
|
+
occlusion.push(bytes[at + 11] / 255);
|
|
12336
13407
|
}
|
|
12337
13408
|
const indices = [];
|
|
12338
13409
|
const indexAt = headerBytes + count * vertexBytes;
|
|
@@ -12341,7 +13412,7 @@ function decodeTileflowTreeModel(text2, level) {
|
|
|
12341
13412
|
if (vertex >= count) return void 0;
|
|
12342
13413
|
indices.push(vertex);
|
|
12343
13414
|
}
|
|
12344
|
-
return { proportion, positions, normals, parts, shades, occlusion
|
|
13415
|
+
return { proportion, positions, normals, parts, shades, occlusion, indices };
|
|
12345
13416
|
}
|
|
12346
13417
|
|
|
12347
13418
|
// src/trees-3d-shaders.ts
|
|
@@ -12404,9 +13475,20 @@ vec3 sceneOf(vec3 local) {
|
|
|
12404
13475
|
return vec3((a_tree_pos + u_offset) * u_metres * vec2(1.0, -1.0), 0.0) + local;
|
|
12405
13476
|
}
|
|
12406
13477
|
`;
|
|
13478
|
+
var treeDither = `
|
|
13479
|
+
// An ordered dither shared by visible and depth passes, so faded trees cast faded shadows.
|
|
13480
|
+
float dither(vec2 p) {
|
|
13481
|
+
ivec2 i = ivec2(mod(p, 4.0));
|
|
13482
|
+
int index = i.x + i.y * 4;
|
|
13483
|
+
float table[16] = float[16](0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0);
|
|
13484
|
+
return (table[index] + 0.5) / 16.0;
|
|
13485
|
+
}`;
|
|
12407
13486
|
var tileflowTreeDepthVertex = `#version 300 es
|
|
12408
13487
|
${attributes3}
|
|
12409
13488
|
uniform mat4 u_light_matrix;
|
|
13489
|
+
uniform vec2 u_centre;
|
|
13490
|
+
uniform vec2 u_reach;
|
|
13491
|
+
out float v_fade;
|
|
12410
13492
|
${placement2}
|
|
12411
13493
|
void main() {
|
|
12412
13494
|
float growth = growthOf();
|
|
@@ -12415,10 +13497,17 @@ void main() {
|
|
|
12415
13497
|
return;
|
|
12416
13498
|
}
|
|
12417
13499
|
gl_Position = u_light_matrix * vec4(sceneOf(standing(growth)), 1.0);
|
|
13500
|
+
float distance = length((a_tree_pos - u_centre) * u_metres);
|
|
13501
|
+
v_fade = 1.0 - smoothstep(u_reach.x, u_reach.y, distance);
|
|
12418
13502
|
}`;
|
|
12419
13503
|
var tileflowTreeDepthFragment = `#version 300 es
|
|
12420
13504
|
precision highp float;
|
|
12421
|
-
|
|
13505
|
+
in float v_fade;
|
|
13506
|
+
uniform float u_fade;
|
|
13507
|
+
${treeDither}
|
|
13508
|
+
void main() {
|
|
13509
|
+
if (v_fade * u_fade < dither(gl_FragCoord.xy)) discard;
|
|
13510
|
+
}`;
|
|
12422
13511
|
var tileflowTreeVertex = (projected) => `#version 300 es
|
|
12423
13512
|
${projected}
|
|
12424
13513
|
${attributes3}
|
|
@@ -12468,31 +13557,10 @@ uniform float u_toon; // 1 for light in bands, as a drawing has it
|
|
|
12468
13557
|
uniform vec3 u_bark;
|
|
12469
13558
|
uniform vec3 u_leaves[4]; // the form's colours, sunlit
|
|
12470
13559
|
uniform float u_leaf_count;
|
|
12471
|
-
uniform highp sampler2DShadow u_shadow_map;
|
|
12472
|
-
uniform vec2 u_shadow_texel;
|
|
12473
|
-
uniform float u_shadow;
|
|
12474
|
-
uniform float u_depth_unit;
|
|
12475
13560
|
out vec4 fragColor;
|
|
13561
|
+
${tileflowShadowLookup}
|
|
12476
13562
|
|
|
12477
|
-
|
|
12478
|
-
if (u_shadow <= 0.0 || c.x <= 0.0 || c.y <= 0.0 || c.x >= 1.0 || c.y >= 1.0 || c.z >= 1.0)
|
|
12479
|
-
return 1.0;
|
|
12480
|
-
float z = c.z - bias * u_depth_unit;
|
|
12481
|
-
vec2 o = u_shadow_texel * 0.75;
|
|
12482
|
-
return 0.25 * (
|
|
12483
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, -o.y), z)) +
|
|
12484
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, -o.y), z)) +
|
|
12485
|
-
texture(u_shadow_map, vec3(c.xy + vec2(-o.x, o.y), z)) +
|
|
12486
|
-
texture(u_shadow_map, vec3(c.xy + vec2(o.x, o.y), z)));
|
|
12487
|
-
}
|
|
12488
|
-
|
|
12489
|
-
// An ordered dither, so a fading tree thins out instead of turning see-through.
|
|
12490
|
-
float dither(vec2 p) {
|
|
12491
|
-
ivec2 i = ivec2(mod(p, 4.0));
|
|
12492
|
-
int index = i.x + i.y * 4;
|
|
12493
|
-
float table[16] = float[16](0.0, 8.0, 2.0, 10.0, 12.0, 4.0, 14.0, 6.0, 3.0, 11.0, 1.0, 9.0, 15.0, 7.0, 13.0, 5.0);
|
|
12494
|
-
return (table[index] + 0.5) / 16.0;
|
|
12495
|
-
}
|
|
13563
|
+
${treeDither}
|
|
12496
13564
|
|
|
12497
13565
|
vec3 leafColour() {
|
|
12498
13566
|
float count = max(u_leaf_count, 1.0);
|
|
@@ -12536,9 +13604,10 @@ void main() {
|
|
|
12536
13604
|
vec3 roof = u_sky_light + u_sun_light * max(u_sun_dir.z, 0.0);
|
|
12537
13605
|
vec3 colour = base * min(pow(light / max(roof, vec3(1e-4)), vec3(1.0 / 2.2)), vec3(1.5));
|
|
12538
13606
|
// At night the tops of crowns keep near their theme's colour, as a night theme draws them, so
|
|
12539
|
-
// trees stay apart from the dark ground.
|
|
13607
|
+
// trees stay apart from the dark ground. The lift is neutral: colour comes from the material
|
|
13608
|
+
// and scene illumination, so a grey tree in neutral light stays grey.
|
|
12540
13609
|
float crownTop = bark ? 0.0 : smoothstep(0.1, 0.9, normal.z);
|
|
12541
|
-
colour = mix(colour, base * 0.95 + vec3(0.
|
|
13610
|
+
colour = mix(colour, base * 0.95 + vec3(0.03), u_night * 0.45 * crownTop);
|
|
12542
13611
|
fragColor = vec4(colour, 1.0);
|
|
12543
13612
|
}`;
|
|
12544
13613
|
var tileflowTreeEdgeVertex = (projected) => `#version 300 es
|
|
@@ -12589,6 +13658,40 @@ void main() {
|
|
|
12589
13658
|
fragColor = vec4(u_edge_colour, 1.0);
|
|
12590
13659
|
}`;
|
|
12591
13660
|
|
|
13661
|
+
// src/trees-3d-view.ts
|
|
13662
|
+
function tileflowTreeView(camera) {
|
|
13663
|
+
const n = 2 ** camera.tileZoom;
|
|
13664
|
+
const phi = camera.lat * Math.PI / 180;
|
|
13665
|
+
const centre = [
|
|
13666
|
+
(camera.lng + 180) / 360 * n,
|
|
13667
|
+
(1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 * n
|
|
13668
|
+
];
|
|
13669
|
+
const tileMetres = 40075016686e-3 * Math.cos(phi) / n;
|
|
13670
|
+
const far = Math.min(
|
|
13671
|
+
3e3,
|
|
13672
|
+
Math.max(500, 900 * 2 ** (16 - camera.zoom) * (1 + camera.pitch / 90))
|
|
13673
|
+
);
|
|
13674
|
+
const reach = [far * 0.7, far];
|
|
13675
|
+
const base = camera.zoom >= 16.5 ? 15 : camera.zoom >= 16 ? 12 : camera.zoom >= 15.5 ? 9 : 7;
|
|
13676
|
+
return {
|
|
13677
|
+
centre,
|
|
13678
|
+
metres: tileMetres / 8192,
|
|
13679
|
+
reach,
|
|
13680
|
+
// Camera movement changes instance density, mesh selection and the per-tree fade.
|
|
13681
|
+
key: `${centre.join()}:${reach.join()}:${base}`,
|
|
13682
|
+
tile(x3, y2) {
|
|
13683
|
+
const [cx, cy] = centre;
|
|
13684
|
+
const distance = Math.hypot(Math.max(x3, Math.min(cx, x3 + 1)) - cx, Math.max(y2, Math.min(cy, y2 + 1)) - cy) * tileMetres;
|
|
13685
|
+
if (distance > far) return void 0;
|
|
13686
|
+
const fall = distance > reach[0] ? Math.ceil((distance - reach[0]) / (far - reach[0]) * 6) : 0;
|
|
13687
|
+
return {
|
|
13688
|
+
level: distance < reach[0] * 0.45 ? "near" : "far",
|
|
13689
|
+
rank: Math.max(2, Math.min(15, base - fall))
|
|
13690
|
+
};
|
|
13691
|
+
}
|
|
13692
|
+
};
|
|
13693
|
+
}
|
|
13694
|
+
|
|
12592
13695
|
// src/trees-3d-browser.ts
|
|
12593
13696
|
var tileflowTrees3DLayerId = "tileflow-trees-3d";
|
|
12594
13697
|
var preferredZoom3 = 15;
|
|
@@ -12598,7 +13701,7 @@ var leafSlots = 4;
|
|
|
12598
13701
|
var finishes = { faceted: { round: 0.5 }, smooth: { round: 1 }, toon: { round: 0.3 } };
|
|
12599
13702
|
function createTileflowTrees3D(options) {
|
|
12600
13703
|
const { map } = options;
|
|
12601
|
-
const minzoom = options.minzoom ??
|
|
13704
|
+
const minzoom = options.minzoom ?? tileflowSceneMinzoom;
|
|
12602
13705
|
const maxTiles = Math.round(options.maxTiles ?? 25);
|
|
12603
13706
|
if (!Number.isFinite(minzoom) || minzoom < 0 || minzoom > 24)
|
|
12604
13707
|
throw new RangeError("Tileflow 3D trees minzoom must be a zoom from 0 to 24.");
|
|
@@ -12610,7 +13713,14 @@ function createTileflowTrees3D(options) {
|
|
|
12610
13713
|
let gl;
|
|
12611
13714
|
let programs = {};
|
|
12612
13715
|
let variant;
|
|
13716
|
+
const programVariants = /* @__PURE__ */ new Map();
|
|
13717
|
+
let fallbackStyle;
|
|
13718
|
+
let fallbackSun;
|
|
13719
|
+
function standaloneStyle() {
|
|
13720
|
+
return fallbackStyle ??= map.getStyle() ?? {};
|
|
13721
|
+
}
|
|
12613
13722
|
let meshes;
|
|
13723
|
+
let hedgeMesh;
|
|
12614
13724
|
const modelMeshes = /* @__PURE__ */ new Map();
|
|
12615
13725
|
let ground;
|
|
12616
13726
|
let disposed = false;
|
|
@@ -12623,9 +13733,7 @@ function createTileflowTrees3D(options) {
|
|
|
12623
13733
|
let design;
|
|
12624
13734
|
function vegetationOnScreen() {
|
|
12625
13735
|
const read = options.controller?.getThemeMetadata?.(tileflowVegetationMetadataKey) ?? {
|
|
12626
|
-
values: [
|
|
12627
|
-
map.getStyle()?.metadata?.[tileflowVegetationMetadataKey]
|
|
12628
|
-
],
|
|
13736
|
+
values: [standaloneStyle().metadata?.[tileflowVegetationMetadataKey]],
|
|
12629
13737
|
position: 0
|
|
12630
13738
|
};
|
|
12631
13739
|
if (!design || design.values.length !== read.values.length || design.values.some((value, index) => value !== read.values[index]))
|
|
@@ -12669,7 +13777,7 @@ function createTileflowTrees3D(options) {
|
|
|
12669
13777
|
const make = (source, layers) => createCameraTiles({
|
|
12670
13778
|
map,
|
|
12671
13779
|
source,
|
|
12672
|
-
layers: [layers.tree, layers.canopy].filter((name) => !!name),
|
|
13780
|
+
layers: [layers.tree, layers.canopy, layers.hedge].filter((name) => !!name),
|
|
12673
13781
|
zoom: preferredZoom3,
|
|
12674
13782
|
minzoom,
|
|
12675
13783
|
maxTiles,
|
|
@@ -12678,9 +13786,10 @@ function createTileflowTrees3D(options) {
|
|
|
12678
13786
|
build(tile, tileMetres) {
|
|
12679
13787
|
const trees = layers.tree ? tile.layers[layers.tree] : void 0;
|
|
12680
13788
|
const canopy = layers.canopy ? tile.layers[layers.canopy] : void 0;
|
|
12681
|
-
|
|
13789
|
+
const hedges = layers.hedge ? tile.layers[layers.hedge] : void 0;
|
|
13790
|
+
if (!trees && !canopy && !hedges) return emptyTile3;
|
|
12682
13791
|
const placement3 = placeTileflowTrees(
|
|
12683
|
-
{ ...trees ? { trees } : {}, ...canopy ? { canopy } : {} },
|
|
13792
|
+
{ ...trees ? { trees } : {}, ...canopy ? { canopy } : {}, ...hedges ? { hedges } : {} },
|
|
12684
13793
|
{
|
|
12685
13794
|
tile,
|
|
12686
13795
|
tileMetres,
|
|
@@ -12695,7 +13804,7 @@ function createTileflowTrees3D(options) {
|
|
|
12695
13804
|
)
|
|
12696
13805
|
}
|
|
12697
13806
|
);
|
|
12698
|
-
return placement3.trees.length > 0 ? { placement: placement3 } : emptyTile3;
|
|
13807
|
+
return placement3.trees.length > 0 || placement3.hedges.length > 0 ? { placement: placement3 } : emptyTile3;
|
|
12699
13808
|
},
|
|
12700
13809
|
empty: emptyTile3,
|
|
12701
13810
|
release(value) {
|
|
@@ -12709,7 +13818,8 @@ function createTileflowTrees3D(options) {
|
|
|
12709
13818
|
tiles.push(
|
|
12710
13819
|
make(described.source, {
|
|
12711
13820
|
tree: described.layers.tree,
|
|
12712
|
-
...canopyElsewhere || !described.layers.canopy ? {} : { canopy: described.layers.canopy }
|
|
13821
|
+
...canopyElsewhere || !described.layers.canopy ? {} : { canopy: described.layers.canopy },
|
|
13822
|
+
...described.layers.hedge ? { hedge: described.layers.hedge } : {}
|
|
12713
13823
|
})
|
|
12714
13824
|
);
|
|
12715
13825
|
if (canopyElsewhere)
|
|
@@ -12721,6 +13831,10 @@ function createTileflowTrees3D(options) {
|
|
|
12721
13831
|
if (!tile.gl) return;
|
|
12722
13832
|
for (const { vao } of tile.gl.vaos.values()) context.deleteVertexArray(vao);
|
|
12723
13833
|
context.deleteBuffer(tile.gl.vbo);
|
|
13834
|
+
if (tile.gl.hedge) {
|
|
13835
|
+
context.deleteVertexArray(tile.gl.hedge.vao);
|
|
13836
|
+
context.deleteBuffer(tile.gl.hedge.vbo);
|
|
13837
|
+
}
|
|
12724
13838
|
tile.gl = void 0;
|
|
12725
13839
|
}
|
|
12726
13840
|
function drawn() {
|
|
@@ -12731,11 +13845,12 @@ function createTileflowTrees3D(options) {
|
|
|
12731
13845
|
return entries;
|
|
12732
13846
|
}
|
|
12733
13847
|
function upload(context) {
|
|
12734
|
-
if (!meshes) return;
|
|
13848
|
+
if (!meshes || !hedgeMesh) return;
|
|
12735
13849
|
let budget = 2;
|
|
12736
13850
|
for (const set of tiles)
|
|
12737
13851
|
for (const { value: tile } of set.ready()) {
|
|
12738
|
-
if (tile.gl || tile.placement.trees.length === 0)
|
|
13852
|
+
if (tile.gl || tile.placement.trees.length === 0 && tile.placement.hedges.length === 0)
|
|
13853
|
+
continue;
|
|
12739
13854
|
if (budget-- <= 0) {
|
|
12740
13855
|
map.triggerRepaint();
|
|
12741
13856
|
return;
|
|
@@ -12758,9 +13873,21 @@ function createTileflowTrees3D(options) {
|
|
|
12758
13873
|
vaos.set(`${form}:${model}:${level}`, { vao, count: mesh.count, crown });
|
|
12759
13874
|
}
|
|
12760
13875
|
});
|
|
13876
|
+
let hedge;
|
|
13877
|
+
if (tile.placement.hedges.length > 0) {
|
|
13878
|
+
const hedgeVbo = context.createBuffer();
|
|
13879
|
+
context.bindBuffer(context.ARRAY_BUFFER, hedgeVbo);
|
|
13880
|
+
context.bufferData(context.ARRAY_BUFFER, tile.placement.hedges, context.STATIC_DRAW);
|
|
13881
|
+
const vao = context.createVertexArray();
|
|
13882
|
+
context.bindVertexArray(vao);
|
|
13883
|
+
bindMesh(context, hedgeMesh.buffer);
|
|
13884
|
+
bindTrees(context, hedgeVbo, 0);
|
|
13885
|
+
hedge = { vao, vbo: hedgeVbo, count: hedgeMesh.count, crown: 1 };
|
|
13886
|
+
}
|
|
12761
13887
|
context.bindVertexArray(null);
|
|
12762
|
-
tile.gl = { vbo, vaos };
|
|
13888
|
+
tile.gl = { vbo, vaos, ...hedge ? { hedge } : {} };
|
|
12763
13889
|
generation += 1;
|
|
13890
|
+
map.triggerRepaint();
|
|
12764
13891
|
}
|
|
12765
13892
|
}
|
|
12766
13893
|
function modelOf(context, form, model, level) {
|
|
@@ -12811,7 +13938,7 @@ function createTileflowTrees3D(options) {
|
|
|
12811
13938
|
instance(7, 2, context.UNSIGNED_BYTE, true, 12);
|
|
12812
13939
|
}
|
|
12813
13940
|
function sunNow() {
|
|
12814
|
-
return options.controller?.getSun?.() ?? resolveTileflowSun(
|
|
13941
|
+
return options.controller?.getSun?.() ?? (fallbackSun ??= resolveTileflowSun(tileflowStyleSunPreset(standaloneStyle())));
|
|
12815
13942
|
}
|
|
12816
13943
|
const parsedColours = /* @__PURE__ */ new Map();
|
|
12817
13944
|
function rgb(colour) {
|
|
@@ -12854,6 +13981,7 @@ function createTileflowTrees3D(options) {
|
|
|
12854
13981
|
broadleaf: slots(theme?.colors.broadleaf ?? ["#6f9a5b"]),
|
|
12855
13982
|
conifer: slots(theme?.colors.conifer ?? ["#557f52"]),
|
|
12856
13983
|
palm: slots(theme?.colors.palm ?? ["#7fa45f"]),
|
|
13984
|
+
hedge: slots(theme?.colors.hedge ?? ["#5f8a55"]),
|
|
12857
13985
|
size: [[theme?.heightScale ?? 1, theme?.crownScale ?? 1, 0]],
|
|
12858
13986
|
forms: tileflowTreeForms.map((form) => [
|
|
12859
13987
|
theme?.forms[form]?.heightScale ?? 1,
|
|
@@ -12872,6 +14000,7 @@ function createTileflowTrees3D(options) {
|
|
|
12872
14000
|
broadleaf: mixList(x3.broadleaf, y2.broadleaf),
|
|
12873
14001
|
conifer: mixList(x3.conifer, y2.conifer),
|
|
12874
14002
|
palm: mixList(x3.palm, y2.palm),
|
|
14003
|
+
hedge: mixList(x3.hedge, y2.hedge),
|
|
12875
14004
|
size,
|
|
12876
14005
|
/** Each form's height and crown scale: the map's times the form's own. */
|
|
12877
14006
|
sizes: Object.fromEntries(
|
|
@@ -12890,27 +14019,22 @@ function createTileflowTrees3D(options) {
|
|
|
12890
14019
|
return scale;
|
|
12891
14020
|
}
|
|
12892
14021
|
function fadeIn() {
|
|
12893
|
-
|
|
12894
|
-
|
|
12895
|
-
|
|
12896
|
-
|
|
12897
|
-
|
|
12898
|
-
|
|
12899
|
-
|
|
12900
|
-
|
|
12901
|
-
|
|
12902
|
-
const zoom = map.getZoom();
|
|
12903
|
-
const base = zoom >= 16.5 ? 15 : zoom >= 16 ? 12 : zoom >= 15.5 ? 9 : 7;
|
|
12904
|
-
const [start, end] = reach();
|
|
12905
|
-
const fall = distance > start ? Math.ceil((distance - start) / (end - start) * 6) : 0;
|
|
12906
|
-
return Math.max(2, Math.min(15, base - fall));
|
|
14022
|
+
return tileflowSceneOpacity(map.getZoom(), minzoom);
|
|
14023
|
+
}
|
|
14024
|
+
function view() {
|
|
14025
|
+
return tileflowTreeView({
|
|
14026
|
+
...map.getCenter(),
|
|
14027
|
+
zoom: map.getZoom(),
|
|
14028
|
+
pitch: map.getPitch(),
|
|
14029
|
+
tileZoom: preferredZoom3
|
|
14030
|
+
});
|
|
12907
14031
|
}
|
|
12908
14032
|
const caster = {
|
|
12909
14033
|
groundOrder: 1,
|
|
12910
14034
|
bounds() {
|
|
12911
14035
|
const shown = visibleScale();
|
|
12912
14036
|
const entries = drawn();
|
|
12913
|
-
if (shown <= 0 || entries.length === 0 ||
|
|
14037
|
+
if (shown <= 0 || entries.length === 0 || fadeIn() <= 0) return void 0;
|
|
12914
14038
|
let [left, top, right, bottom, tallest] = [Infinity, Infinity, -Infinity, -Infinity, 0];
|
|
12915
14039
|
for (const { x: x3, y: y2, set, value } of entries) {
|
|
12916
14040
|
if (set.zoom !== preferredZoom3) continue;
|
|
@@ -12928,8 +14052,8 @@ function createTileflowTrees3D(options) {
|
|
|
12928
14052
|
tallest: tallest * shown * size[0]
|
|
12929
14053
|
};
|
|
12930
14054
|
},
|
|
12931
|
-
//
|
|
12932
|
-
key: () => `trees:${visibleScale().toFixed(4)}:${generation}:${programs.depth ? 1 : 0}:${Object.values(
|
|
14055
|
+
// Camera-dependent density, detail and fading also change the trees casting shadows.
|
|
14056
|
+
key: () => `trees:${view().key}:${fadeIn().toFixed(4)}:${visibleScale().toFixed(4)}:${generation}:${programs.depth ? 1 : 0}:${Object.values(
|
|
12933
14057
|
colours().sizes
|
|
12934
14058
|
).flat().map((value) => value.toFixed(3)).join()}:${drawn().map(({ x: x3, y: y2 }) => `${x3}/${y2}`).join(";")}`,
|
|
12935
14059
|
drawDepth(context, frame) {
|
|
@@ -12941,16 +14065,38 @@ function createTileflowTrees3D(options) {
|
|
|
12941
14065
|
context.uniform1f(u.u_metres, frame.metres);
|
|
12942
14066
|
context.uniformMatrix4fv(u.u_light_matrix, false, frame.light.matrix);
|
|
12943
14067
|
const sizes = colours().sizes;
|
|
14068
|
+
const camera = view();
|
|
14069
|
+
context.uniform1f(u.u_fade, 1);
|
|
14070
|
+
context.uniform2fv(u.u_reach, [...camera.reach]);
|
|
12944
14071
|
for (const { x: x3, y: y2, set, value } of drawn()) {
|
|
14072
|
+
const selection = camera.tile(x3, y2);
|
|
14073
|
+
if (!selection) continue;
|
|
12945
14074
|
context.uniform2f(u.u_offset, ...frame.offsetOf({ x: x3, y: y2 }, set.zoom));
|
|
14075
|
+
context.uniform2f(
|
|
14076
|
+
u.u_centre,
|
|
14077
|
+
(camera.centre[0] - x3) * tileflowSceneExtent,
|
|
14078
|
+
(camera.centre[1] - y2) * tileflowSceneExtent
|
|
14079
|
+
);
|
|
12946
14080
|
for (const form of tileflowTreeForms)
|
|
12947
14081
|
value.placement.forms[form].variants.forEach((range, model) => {
|
|
12948
|
-
const
|
|
12949
|
-
|
|
14082
|
+
const instances = range.upTo[selection.rank] ?? 0;
|
|
14083
|
+
const drawing = value.gl.vaos.get(`${form}:${model}:${selection.level}`);
|
|
14084
|
+
if (instances === 0 || !drawing) return;
|
|
12950
14085
|
context.uniform2f(u.u_size_scale, sizes[form][0], sizes[form][1] * drawing.crown);
|
|
12951
14086
|
context.bindVertexArray(drawing.vao);
|
|
12952
|
-
context.drawArraysInstanced(context.TRIANGLES, 0, drawing.count,
|
|
14087
|
+
context.drawArraysInstanced(context.TRIANGLES, 0, drawing.count, instances);
|
|
12953
14088
|
});
|
|
14089
|
+
const hedge = value.gl.hedge;
|
|
14090
|
+
if (hedge) {
|
|
14091
|
+
context.uniform2f(u.u_size_scale, 1, 1);
|
|
14092
|
+
context.bindVertexArray(hedge.vao);
|
|
14093
|
+
context.drawArraysInstanced(
|
|
14094
|
+
context.TRIANGLES,
|
|
14095
|
+
0,
|
|
14096
|
+
hedge.count,
|
|
14097
|
+
value.placement.hedges.length / tileflowTreeBytes
|
|
14098
|
+
);
|
|
14099
|
+
}
|
|
12954
14100
|
}
|
|
12955
14101
|
}
|
|
12956
14102
|
};
|
|
@@ -12968,17 +14114,13 @@ function createTileflowTrees3D(options) {
|
|
|
12968
14114
|
drawnLast = 0;
|
|
12969
14115
|
if (entries.length === 0 || !meshes || !programs.trees) return;
|
|
12970
14116
|
const sun = sunNow();
|
|
12971
|
-
const
|
|
14117
|
+
const sunLight = tileflowSceneLight(sun);
|
|
14118
|
+
const light = { ...sunLight, strength: sunLight.strength * fadeIn() };
|
|
12972
14119
|
const palette2 = colours();
|
|
12973
14120
|
const frame = scene.frame();
|
|
12974
|
-
const
|
|
12975
|
-
const
|
|
12976
|
-
const
|
|
12977
|
-
const cx = (centre.lng + 180) / 360 * n;
|
|
12978
|
-
const phi = centre.lat * Math.PI / 180;
|
|
12979
|
-
const cy = (1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 * n;
|
|
12980
|
-
const metres = frame?.metres ?? 40075016686e-3 * Math.cos(phi) / n / tileflowSceneExtent;
|
|
12981
|
-
const [near, far] = reach();
|
|
14121
|
+
const camera = view();
|
|
14122
|
+
const [cx, cy] = camera.centre;
|
|
14123
|
+
const metres = frame?.metres ?? camera.metres;
|
|
12982
14124
|
const program = programs.trees;
|
|
12983
14125
|
const u = program.uniforms;
|
|
12984
14126
|
context.useProgram(program.program);
|
|
@@ -12997,7 +14139,7 @@ function createTileflowTrees3D(options) {
|
|
|
12997
14139
|
context.uniform3fv(u.u_sky_light, light.skyLight);
|
|
12998
14140
|
context.uniform1f(u.u_night, light.night);
|
|
12999
14141
|
context.uniform1f(u.u_fade, fadeIn());
|
|
13000
|
-
context.uniform2fv(u.u_reach, reach
|
|
14142
|
+
context.uniform2fv(u.u_reach, [...camera.reach]);
|
|
13001
14143
|
context.uniform3fv(u.u_bark, palette2.bark);
|
|
13002
14144
|
context.uniform1f(u.u_leaf_count, leafSlots);
|
|
13003
14145
|
scene.bindShadow(context, u, light.strength);
|
|
@@ -13012,10 +14154,9 @@ function createTileflowTrees3D(options) {
|
|
|
13012
14154
|
(cx - x3) * tileflowSceneExtent,
|
|
13013
14155
|
(cy - y2) * tileflowSceneExtent
|
|
13014
14156
|
);
|
|
13015
|
-
const
|
|
13016
|
-
if (
|
|
13017
|
-
const level
|
|
13018
|
-
const rank = rankOf(distance);
|
|
14157
|
+
const selection = camera.tile(x3, y2);
|
|
14158
|
+
if (!selection) continue;
|
|
14159
|
+
const { level, rank } = selection;
|
|
13019
14160
|
for (const form of tileflowTreeForms) {
|
|
13020
14161
|
const leaves = form === "palm" ? palette2.palm : form === "conifer" || form === "columnar" ? palette2.conifer : palette2.broadleaf;
|
|
13021
14162
|
value.placement.forms[form].variants.forEach((range, model) => {
|
|
@@ -13023,14 +14164,26 @@ function createTileflowTrees3D(options) {
|
|
|
13023
14164
|
const drawing = value.gl.vaos.get(`${form}:${model}:${level}`);
|
|
13024
14165
|
if (instances === 0 || !drawing) return;
|
|
13025
14166
|
context.uniform3fv(u.u_leaves, leaves.flat());
|
|
13026
|
-
const size =
|
|
13027
|
-
|
|
14167
|
+
const size = [
|
|
14168
|
+
palette2.sizes[form][0],
|
|
14169
|
+
palette2.sizes[form][1] * drawing.crown
|
|
14170
|
+
];
|
|
14171
|
+
context.uniform2f(u.u_size_scale, ...size);
|
|
13028
14172
|
context.bindVertexArray(drawing.vao);
|
|
13029
14173
|
context.drawArraysInstanced(context.TRIANGLES, 0, drawing.count, instances);
|
|
13030
14174
|
count += instances;
|
|
13031
|
-
draws.push({ x: x3, y: y2, offset,
|
|
14175
|
+
draws.push({ x: x3, y: y2, offset, leaves, size, instances, drawing });
|
|
13032
14176
|
});
|
|
13033
14177
|
}
|
|
14178
|
+
const hedge = value.gl.hedge;
|
|
14179
|
+
if (hedge) {
|
|
14180
|
+
const instances = value.placement.hedges.length / tileflowTreeBytes;
|
|
14181
|
+
context.uniform3fv(u.u_leaves, palette2.hedge.flat());
|
|
14182
|
+
context.uniform2f(u.u_size_scale, 1, 1);
|
|
14183
|
+
context.bindVertexArray(hedge.vao);
|
|
14184
|
+
context.drawArraysInstanced(context.TRIANGLES, 0, hedge.count, instances);
|
|
14185
|
+
draws.push({ x: x3, y: y2, offset, leaves: palette2.hedge, size: [1, 1], instances, drawing: hedge });
|
|
14186
|
+
}
|
|
13034
14187
|
}
|
|
13035
14188
|
drawnLast = count;
|
|
13036
14189
|
const edges = palette2.edges;
|
|
@@ -13044,7 +14197,7 @@ function createTileflowTrees3D(options) {
|
|
|
13044
14197
|
context.uniform1f(e.u_metres, metres);
|
|
13045
14198
|
context.uniform1f(e.u_round, finishes[palette2.finish].round);
|
|
13046
14199
|
context.uniform1f(e.u_fade, fadeIn());
|
|
13047
|
-
context.uniform2fv(e.u_reach, reach
|
|
14200
|
+
context.uniform2fv(e.u_reach, [...camera.reach]);
|
|
13048
14201
|
context.uniform2f(e.u_viewport, context.drawingBufferWidth, context.drawingBufferHeight);
|
|
13049
14202
|
context.uniform1f(e.u_edge_width, edges * 1.5 * pixelRatio(context));
|
|
13050
14203
|
let tile = "";
|
|
@@ -13059,16 +14212,14 @@ function createTileflowTrees3D(options) {
|
|
|
13059
14212
|
(cy - draw2.y) * tileflowSceneExtent
|
|
13060
14213
|
);
|
|
13061
14214
|
}
|
|
13062
|
-
const leaves = draw2.form === "palm" ? palette2.palm : draw2.form === "conifer" || draw2.form === "columnar" ? palette2.conifer : palette2.broadleaf;
|
|
13063
14215
|
const own = palette2.ownEdge;
|
|
13064
14216
|
context.uniform3fv(
|
|
13065
14217
|
e.u_edge_colour,
|
|
13066
14218
|
[0, 1, 2].map(
|
|
13067
|
-
(channel) => palette2.edgeColor[channel] * own + leaves[0][channel] * 0.4 * (1 - own)
|
|
14219
|
+
(channel) => palette2.edgeColor[channel] * own + draw2.leaves[0][channel] * 0.4 * (1 - own)
|
|
13068
14220
|
)
|
|
13069
14221
|
);
|
|
13070
|
-
|
|
13071
|
-
context.uniform2f(e.u_size_scale, size[0], size[1] * draw2.drawing.crown);
|
|
14222
|
+
context.uniform2f(e.u_size_scale, ...draw2.size);
|
|
13072
14223
|
context.bindVertexArray(draw2.drawing.vao);
|
|
13073
14224
|
context.drawArraysInstanced(context.TRIANGLES, 0, draw2.drawing.count, draw2.instances);
|
|
13074
14225
|
}
|
|
@@ -13138,40 +14289,56 @@ function createTileflowTrees3D(options) {
|
|
|
13138
14289
|
function link(context, shaderData) {
|
|
13139
14290
|
const projected = `${shaderData.vertexShaderPrelude}
|
|
13140
14291
|
${shaderData.define}`;
|
|
13141
|
-
const
|
|
13142
|
-
|
|
14292
|
+
const cached = programVariants.get(projected);
|
|
14293
|
+
if (cached) {
|
|
14294
|
+
programVariants.delete(projected);
|
|
14295
|
+
programVariants.set(projected, cached);
|
|
14296
|
+
programs = cached;
|
|
14297
|
+
variant = projected;
|
|
14298
|
+
return;
|
|
14299
|
+
}
|
|
14300
|
+
const next = {};
|
|
14301
|
+
try {
|
|
14302
|
+
next.trees = compile(
|
|
13143
14303
|
context,
|
|
13144
14304
|
tileflowTreeVertex(projected),
|
|
13145
14305
|
tileflowTreeFragment,
|
|
13146
14306
|
tileflowTreeAttributes
|
|
13147
|
-
)
|
|
13148
|
-
ground
|
|
14307
|
+
);
|
|
14308
|
+
next.ground = compile(
|
|
13149
14309
|
context,
|
|
13150
14310
|
tileflowBuildingGroundVertex(projected),
|
|
13151
14311
|
tileflowBuildingGroundFragment,
|
|
13152
14312
|
["a_pos"]
|
|
13153
|
-
)
|
|
13154
|
-
|
|
13155
|
-
context,
|
|
13156
|
-
tileflowTreeEdgeVertex(projected),
|
|
13157
|
-
tileflowTreeEdgeFragment,
|
|
13158
|
-
tileflowTreeAttributes
|
|
13159
|
-
),
|
|
13160
|
-
depth: programs.depth ?? compile(
|
|
14313
|
+
);
|
|
14314
|
+
next.depth = programs.depth ?? compile(
|
|
13161
14315
|
context,
|
|
13162
14316
|
tileflowTreeDepthVertex,
|
|
13163
14317
|
tileflowTreeDepthFragment,
|
|
13164
14318
|
tileflowTreeAttributes
|
|
13165
|
-
)
|
|
13166
|
-
}
|
|
13167
|
-
|
|
13168
|
-
|
|
13169
|
-
|
|
14319
|
+
);
|
|
14320
|
+
} catch (error) {
|
|
14321
|
+
for (const name of ["trees", "ground"])
|
|
14322
|
+
if (next[name]) context.deleteProgram(next[name].program);
|
|
14323
|
+
throw error;
|
|
14324
|
+
}
|
|
14325
|
+
programVariants.set(projected, next);
|
|
14326
|
+
if (programVariants.size > 4) {
|
|
14327
|
+
const oldest = programVariants.keys().next().value;
|
|
14328
|
+
const expired = programVariants.get(oldest);
|
|
14329
|
+
for (const name of ["trees", "ground", "edges"])
|
|
14330
|
+
if (expired[name]) context.deleteProgram(expired[name].program);
|
|
14331
|
+
programVariants.delete(oldest);
|
|
13170
14332
|
}
|
|
13171
14333
|
programs = next;
|
|
13172
|
-
variant =
|
|
14334
|
+
variant = projected;
|
|
13173
14335
|
}
|
|
13174
14336
|
function createMeshes(context) {
|
|
14337
|
+
const hedge = tileflowHedgeMesh();
|
|
14338
|
+
const hedgeBuffer = context.createBuffer();
|
|
14339
|
+
context.bindBuffer(context.ARRAY_BUFFER, hedgeBuffer);
|
|
14340
|
+
context.bufferData(context.ARRAY_BUFFER, hedge.vertices, context.STATIC_DRAW);
|
|
14341
|
+
hedgeMesh = { buffer: hedgeBuffer, count: hedge.count };
|
|
13175
14342
|
const built = tileflowTreeMeshes();
|
|
13176
14343
|
meshes = Object.fromEntries(
|
|
13177
14344
|
tileflowTreeForms.map((form) => [
|
|
@@ -13220,19 +14387,20 @@ ${shaderData.define}`;
|
|
|
13220
14387
|
onAdd(_2, context) {
|
|
13221
14388
|
if (!isWebGL23(context)) return;
|
|
13222
14389
|
gl = context;
|
|
13223
|
-
createMeshes(context);
|
|
13224
14390
|
},
|
|
13225
14391
|
onRemove(_2, context) {
|
|
13226
14392
|
if (!isWebGL23(context)) return;
|
|
13227
14393
|
for (const set of tiles)
|
|
13228
14394
|
for (const entry of set.all()) if (entry.value) release(context, entry.value);
|
|
13229
|
-
|
|
13230
|
-
|
|
14395
|
+
const owned = new Set([...programVariants.values()].flatMap((value) => Object.values(value)));
|
|
14396
|
+
for (const program of owned) if (program) context.deleteProgram(program.program);
|
|
14397
|
+
programVariants.clear();
|
|
13231
14398
|
if (meshes)
|
|
13232
14399
|
for (const levels of Object.values(meshes))
|
|
13233
14400
|
for (const mesh of Object.values(levels)) context.deleteBuffer(mesh.buffer);
|
|
13234
14401
|
for (const mesh of modelMeshes.values()) if (mesh) context.deleteBuffer(mesh.buffer);
|
|
13235
14402
|
modelMeshes.clear();
|
|
14403
|
+
if (hedgeMesh) context.deleteBuffer(hedgeMesh.buffer);
|
|
13236
14404
|
if (ground) {
|
|
13237
14405
|
context.deleteVertexArray(ground.vao);
|
|
13238
14406
|
context.deleteBuffer(ground.vbo);
|
|
@@ -13242,6 +14410,7 @@ ${shaderData.define}`;
|
|
|
13242
14410
|
scene.release(context);
|
|
13243
14411
|
programs = {};
|
|
13244
14412
|
meshes = void 0;
|
|
14413
|
+
hedgeMesh = void 0;
|
|
13245
14414
|
ground = void 0;
|
|
13246
14415
|
variant = void 0;
|
|
13247
14416
|
gl = void 0;
|
|
@@ -13257,13 +14426,23 @@ ${shaderData.define}`;
|
|
|
13257
14426
|
},
|
|
13258
14427
|
render(context, args) {
|
|
13259
14428
|
if (!isWebGL23(context)) return;
|
|
13260
|
-
|
|
13261
|
-
|
|
13262
|
-
} catch {
|
|
14429
|
+
if (visibleScale() <= 0 || fadeIn() <= 0) {
|
|
14430
|
+
drawnLast = 0;
|
|
13263
14431
|
return;
|
|
13264
14432
|
}
|
|
13265
|
-
|
|
13266
|
-
|
|
14433
|
+
try {
|
|
14434
|
+
const projected = `${args.shaderData.vertexShaderPrelude}
|
|
14435
|
+
${args.shaderData.define}`;
|
|
14436
|
+
if (variant !== projected) link(context, args.shaderData);
|
|
14437
|
+
if (colours().edges > 0.01 && !programs.edges)
|
|
14438
|
+
programs.edges = compile(
|
|
14439
|
+
context,
|
|
14440
|
+
tileflowTreeEdgeVertex(projected),
|
|
14441
|
+
tileflowTreeEdgeFragment,
|
|
14442
|
+
tileflowTreeAttributes
|
|
14443
|
+
);
|
|
14444
|
+
if (!meshes) createMeshes(context);
|
|
14445
|
+
} catch {
|
|
13267
14446
|
return;
|
|
13268
14447
|
}
|
|
13269
14448
|
ensureTiles();
|
|
@@ -13308,6 +14487,8 @@ ${shaderData.define}`;
|
|
|
13308
14487
|
}
|
|
13309
14488
|
}
|
|
13310
14489
|
const onStyle = () => {
|
|
14490
|
+
fallbackStyle = void 0;
|
|
14491
|
+
fallbackSun = void 0;
|
|
13311
14492
|
attach();
|
|
13312
14493
|
if (target > 0) {
|
|
13313
14494
|
standIn(true);
|
|
@@ -13315,7 +14496,7 @@ ${shaderData.define}`;
|
|
|
13315
14496
|
}
|
|
13316
14497
|
};
|
|
13317
14498
|
map.on("styledata", onStyle);
|
|
13318
|
-
map.on("idle",
|
|
14499
|
+
map.on("idle", attach);
|
|
13319
14500
|
attach();
|
|
13320
14501
|
function grow(goal, change) {
|
|
13321
14502
|
if (disposed) return Promise.resolve();
|
|
@@ -13376,20 +14557,20 @@ ${shaderData.define}`;
|
|
|
13376
14557
|
const model = described.models[id];
|
|
13377
14558
|
const decoded = model ? decodeTileflowTreeModel(model.near, "near") : void 0;
|
|
13378
14559
|
const mesh = decoded ? tileflowTreeModelMeshOf(decoded) : built[form].near;
|
|
13379
|
-
const
|
|
14560
|
+
const view2 = new DataView(
|
|
13380
14561
|
mesh.vertices.buffer,
|
|
13381
14562
|
mesh.vertices.byteOffset,
|
|
13382
14563
|
mesh.vertices.byteLength
|
|
13383
14564
|
);
|
|
13384
14565
|
const indices = Array.from({ length: mesh.count }, (_2, i2) => i2);
|
|
13385
14566
|
const preview = inspectModelTriangles(indices, (index) => [
|
|
13386
|
-
|
|
13387
|
-
|
|
13388
|
-
|
|
14567
|
+
view2.getFloat32(index * 24, true),
|
|
14568
|
+
view2.getFloat32(index * 24 + 4, true),
|
|
14569
|
+
view2.getFloat32(index * 24 + 8, true)
|
|
13389
14570
|
]);
|
|
13390
14571
|
const colors = [];
|
|
13391
14572
|
for (let i2 = 0; i2 < mesh.count; i2 += 3) {
|
|
13392
|
-
const part =
|
|
14573
|
+
const part = view2.getUint8(i2 * 24 + 15);
|
|
13393
14574
|
const palette2 = described.colors[form === "conifer" ? "conifer" : form === "palm" ? "palm" : "broadleaf"];
|
|
13394
14575
|
colors.push(part === 0 || part === 3 ? described.colors.bark : palette2[0]);
|
|
13395
14576
|
}
|
|
@@ -13420,13 +14601,15 @@ ${shaderData.define}`;
|
|
|
13420
14601
|
let trees = 0;
|
|
13421
14602
|
let mapped = 0;
|
|
13422
14603
|
let canopy = 0;
|
|
14604
|
+
let hedges = 0;
|
|
13423
14605
|
for (const set of tiles)
|
|
13424
14606
|
for (const { value } of set.ready()) {
|
|
13425
|
-
if (value.placement.trees.length === 0) continue;
|
|
14607
|
+
if (value.placement.trees.length === 0 && value.placement.hedges.length === 0) continue;
|
|
13426
14608
|
count += 1;
|
|
13427
14609
|
trees += value.placement.trees.length / tileflowTreeBytes;
|
|
13428
14610
|
mapped += value.placement.mapped;
|
|
13429
14611
|
canopy += value.placement.canopy;
|
|
14612
|
+
hedges += value.placement.hedges.length / tileflowTreeBytes;
|
|
13430
14613
|
}
|
|
13431
14614
|
return {
|
|
13432
14615
|
declared: vegetation() !== void 0,
|
|
@@ -13434,6 +14617,7 @@ ${shaderData.define}`;
|
|
|
13434
14617
|
trees,
|
|
13435
14618
|
mapped,
|
|
13436
14619
|
canopy,
|
|
14620
|
+
hedges,
|
|
13437
14621
|
drawn: drawnLast
|
|
13438
14622
|
};
|
|
13439
14623
|
},
|
|
@@ -13443,7 +14627,7 @@ ${shaderData.define}`;
|
|
|
13443
14627
|
motion?.done();
|
|
13444
14628
|
standIn(false);
|
|
13445
14629
|
map.off("styledata", onStyle);
|
|
13446
|
-
map.off("idle",
|
|
14630
|
+
map.off("idle", attach);
|
|
13447
14631
|
unregister();
|
|
13448
14632
|
try {
|
|
13449
14633
|
if (map.getLayer(tileflowTrees3DLayerId)) map.removeLayer(tileflowTrees3DLayerId);
|
|
@@ -13460,6 +14644,7 @@ var emptyTile3 = Object.freeze({
|
|
|
13460
14644
|
forms: Object.fromEntries(
|
|
13461
14645
|
tileflowTreeForms.map((form) => [form, { start: 0, count: 0, upTo: new Array(16).fill(0) }])
|
|
13462
14646
|
),
|
|
14647
|
+
hedges: new Uint8Array(0),
|
|
13463
14648
|
tallest: 0,
|
|
13464
14649
|
mapped: 0,
|
|
13465
14650
|
canopy: 0
|
|
@@ -15015,6 +16200,19 @@ function createTileflowThemeController(options) {
|
|
|
15015
16200
|
const blendMetadata = /* @__PURE__ */ new WeakMap();
|
|
15016
16201
|
let nextStyleIdentity = 0;
|
|
15017
16202
|
let current = options.initial;
|
|
16203
|
+
let metadataStyle;
|
|
16204
|
+
const invalidateMetadata = () => {
|
|
16205
|
+
metadataStyle = void 0;
|
|
16206
|
+
};
|
|
16207
|
+
const releaseMetadata = () => {
|
|
16208
|
+
options.map.off("styledata", invalidateMetadata);
|
|
16209
|
+
options.map.off("style.load", invalidateMetadata);
|
|
16210
|
+
options.map.off("remove", releaseMetadata);
|
|
16211
|
+
invalidateMetadata();
|
|
16212
|
+
};
|
|
16213
|
+
options.map.on("styledata", invalidateMetadata);
|
|
16214
|
+
options.map.on("style.load", invalidateMetadata);
|
|
16215
|
+
options.map.on("remove", releaseMetadata);
|
|
15018
16216
|
let shown = { style: options.initial.style };
|
|
15019
16217
|
let activeBlend;
|
|
15020
16218
|
let pendingBlend;
|
|
@@ -15060,6 +16258,7 @@ function createTileflowThemeController(options) {
|
|
|
15060
16258
|
return {
|
|
15061
16259
|
dispose() {
|
|
15062
16260
|
disposed = true;
|
|
16261
|
+
releaseMetadata();
|
|
15063
16262
|
images?.dispose();
|
|
15064
16263
|
requestId += 1;
|
|
15065
16264
|
activeBlend?.blender.dispose();
|
|
@@ -15109,7 +16308,7 @@ function createTileflowThemeController(options) {
|
|
|
15109
16308
|
const blended = activeBlend && blendMetadata.get(activeBlend.plan);
|
|
15110
16309
|
if (activeBlend && blended)
|
|
15111
16310
|
return { values: blended.map(read), position: activeBlend.blender.position };
|
|
15112
|
-
const style = typeof current.style === "string" ? shownStyleDocument() : current.style;
|
|
16311
|
+
const style = typeof current.style === "string" ? metadataStyle ??= shownStyleDocument() : current.style;
|
|
15113
16312
|
return { values: [read(style?.metadata)], position: 0 };
|
|
15114
16313
|
},
|
|
15115
16314
|
getSun() {
|
|
@@ -15308,7 +16507,9 @@ function createTileflowThemeController(options) {
|
|
|
15308
16507
|
const styleInput = target.kind === "theme" ? target.style.style : target.plan.styleAt(target.position());
|
|
15309
16508
|
try {
|
|
15310
16509
|
if (target.kind === "blend") images?.assertBlend(target.plan.images);
|
|
16510
|
+
invalidateMetadata();
|
|
15311
16511
|
await applyMapStyle(options.map, styleInput, timeoutMs);
|
|
16512
|
+
invalidateMetadata();
|
|
15312
16513
|
if (disposed) {
|
|
15313
16514
|
cover?.cancel();
|
|
15314
16515
|
return { status: "superseded", theme: targetTheme };
|
|
@@ -16283,6 +17484,7 @@ export {
|
|
|
16283
17484
|
attachTileflowAtmosphere,
|
|
16284
17485
|
attachTileflowFairUseNotice,
|
|
16285
17486
|
attachTileflowMapLifecycle,
|
|
17487
|
+
createBoundaryFeatureState,
|
|
16286
17488
|
createTileflowBuildings3D,
|
|
16287
17489
|
createTileflowDepthMarkers,
|
|
16288
17490
|
createTileflowGroundLayers,
|