@tileflow/dev 0.1.0-alpha.33 → 0.1.0-alpha.34
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 +1 -0
- package/dist/artifacts.d.ts +3 -3
- package/dist/artifacts.js +4 -2
- package/dist/{chunk-WJOF6USY.js → chunk-2F7LOYQI.js} +3 -1
- package/dist/{chunk-QYU4OW5A.js → chunk-5RT4CHSK.js} +485 -73
- package/dist/fonts.d.ts +1 -1
- package/dist/{icons-BdOehQmB.d.ts → icons-BIHpw5Zy.d.ts} +15 -22
- package/dist/icons.d.ts +1 -1
- package/dist/icons.js +1 -3
- package/dist/preview.js +2 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +2 -2
- package/docs/bounded-vector-feature-inspection.md +1 -1
- package/docs/building-models.md +179 -0
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -258,6 +258,7 @@ catalog publication and lock maintenance stay in the `tileflow` CLI, which is th
|
|
|
258
258
|
|
|
259
259
|
These references are also included under `docs/` in the installed package:
|
|
260
260
|
|
|
261
|
+
- [Building model preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/building-models.md): colour slots, architectural roles, ambient visibility and compatibility.
|
|
261
262
|
- [Native artifact preparation](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/native-artifacts.md): static compatibility, renderer-qualified output, and limitations.
|
|
262
263
|
- [Artifact lifecycle](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/artifact-lifecycle.md): generations, immutable snapshots, provenance, and cleanup.
|
|
263
264
|
- [Local comparison and inspection](https://github.com/tileflow/tileflow-sdk/blob/main/packages/dev/docs/local-comparison-workbench-and-compiler-inspection.md): Map DevTools controls and preview-only models.
|
package/dist/artifacts.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { MapLibreStyle, TileflowViewConfig, TileflowStyleOptions } from '@tileflow/core';
|
|
2
|
-
export { tileflowLandmarkRegionOf, tileflowLandmarkRegionZoom, tileflowLandmarkTileOf, tileflowLandmarkTileZoom } from '@tileflow/core';
|
|
2
|
+
export { TileflowLandmarkFootprint, readTileflowLandmarkTileFootprints, tileflowLandmarkRegionOf, tileflowLandmarkRegionZoom, tileflowLandmarkTileOf, tileflowLandmarkTileZoom } from '@tileflow/core';
|
|
3
3
|
import { TileflowBuildCatalog, TileflowBuildStyles, TileflowMapBuildManifestV1, TileflowRendererDeploymentArtifact, TileflowPreparedMapAssets, TileflowStyleInspection, TileflowMapBuildProvenanceV1 } from '@tileflow/core/build';
|
|
4
4
|
import { TileflowRuntimeManifest } from '@tileflow/core/manifest';
|
|
5
5
|
import { TileflowRenderer, TileflowNativeBuildRecord } from '@tileflow/core/native-profile';
|
|
6
|
-
import { T as TileflowBuildAsset, a as TileflowIconResolutionOptions, P as PreparedTileflowBuildCatalog, b as TileflowSourceCatalog, c as PreparedTileflowCatalog } from './icons-
|
|
7
|
-
export { d as TileflowBuildingModelLevels, e as TileflowLandmarkSource, f as encodeTileflowLandmarkRegion, p as packageTileflowLandmarkTile, g as prepareTileflowBuildingModel } from './icons-
|
|
6
|
+
import { T as TileflowBuildAsset, a as TileflowIconResolutionOptions, P as PreparedTileflowBuildCatalog, b as TileflowSourceCatalog, c as PreparedTileflowCatalog } from './icons-BIHpw5Zy.js';
|
|
7
|
+
export { d as TileflowBuildingModelLevels, e as TileflowLandmarkSource, f as encodeTileflowLandmarkRegion, p as packageTileflowLandmarkTile, g as prepareTileflowBuildingModel } from './icons-BIHpw5Zy.js';
|
|
8
8
|
export { defaultTileflowConfigPath } from './config.js';
|
|
9
9
|
|
|
10
10
|
/**
|
package/dist/artifacts.js
CHANGED
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
parseTileflowLineBackgroundGlyphPath,
|
|
20
20
|
prepareTileflowHostedNativeDeployment,
|
|
21
21
|
prepareTileflowLocalTilesets,
|
|
22
|
+
readTileflowLandmarkTileFootprints,
|
|
22
23
|
refreshTileflowArtifactSession,
|
|
23
24
|
tileflowArtifactInventoryFileName,
|
|
24
25
|
tileflowArtifactPlanSchemaVersion,
|
|
@@ -31,7 +32,7 @@ import {
|
|
|
31
32
|
usesTileflowLineBackgroundGlyphs,
|
|
32
33
|
writeTileflowArtifactPlan,
|
|
33
34
|
writeTileflowBuildArtifacts
|
|
34
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-2F7LOYQI.js";
|
|
35
36
|
import {
|
|
36
37
|
getTileflowAssetBasePath,
|
|
37
38
|
getTileflowAssetFileName,
|
|
@@ -43,7 +44,7 @@ import {
|
|
|
43
44
|
encodeTileflowLandmarkRegion,
|
|
44
45
|
packageTileflowLandmarkTile,
|
|
45
46
|
prepareTileflowBuildingModel
|
|
46
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-5RT4CHSK.js";
|
|
47
48
|
import "./chunk-7C4F6GAX.js";
|
|
48
49
|
import "./chunk-KDFTE4Q5.js";
|
|
49
50
|
import {
|
|
@@ -80,6 +81,7 @@ export {
|
|
|
80
81
|
prepareTileflowBuildingModel,
|
|
81
82
|
prepareTileflowHostedNativeDeployment,
|
|
82
83
|
prepareTileflowLocalTilesets,
|
|
84
|
+
readTileflowLandmarkTileFootprints,
|
|
83
85
|
refreshTileflowArtifactSession,
|
|
84
86
|
resolveTileflowArtifactPublicUrls,
|
|
85
87
|
tileflowArtifactInventoryFileName,
|
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
import {
|
|
9
9
|
getTileflowIconWatchPaths,
|
|
10
10
|
prepareTileflowCatalogIcons
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5RT4CHSK.js";
|
|
12
12
|
import {
|
|
13
13
|
TileflowStyleValidationError,
|
|
14
14
|
normalizeTileflowStyleValidationIssues,
|
|
@@ -2125,6 +2125,7 @@ async function prepareTileflowHostedNativeDeployment(input) {
|
|
|
2125
2125
|
|
|
2126
2126
|
// src/artifacts.ts
|
|
2127
2127
|
import {
|
|
2128
|
+
readTileflowLandmarkTileFootprints,
|
|
2128
2129
|
tileflowLandmarkRegionOf,
|
|
2129
2130
|
tileflowLandmarkRegionZoom,
|
|
2130
2131
|
tileflowLandmarkTileOf,
|
|
@@ -3096,6 +3097,7 @@ export {
|
|
|
3096
3097
|
writeTileflowArtifactPlan,
|
|
3097
3098
|
assertNoLocalTilesetsForProduction,
|
|
3098
3099
|
assertTileflowSelfHostedManifestTarget,
|
|
3100
|
+
readTileflowLandmarkTileFootprints,
|
|
3099
3101
|
tileflowLandmarkRegionOf,
|
|
3100
3102
|
tileflowLandmarkRegionZoom,
|
|
3101
3103
|
tileflowLandmarkTileOf,
|
|
@@ -25,7 +25,7 @@ import {
|
|
|
25
25
|
sha256Hex as sha256Hex3,
|
|
26
26
|
tileflowIconIdPattern,
|
|
27
27
|
tileflowIconPackageLimits as tileflowIconPackageLimits4,
|
|
28
|
-
tileflowModelContentType
|
|
28
|
+
tileflowModelContentType
|
|
29
29
|
} from "@tileflow/core";
|
|
30
30
|
|
|
31
31
|
// src/icon-composition.ts
|
|
@@ -949,17 +949,384 @@ import {
|
|
|
949
949
|
compareCodeUnits as compareCodeUnits4,
|
|
950
950
|
encodeTileflowModels,
|
|
951
951
|
encodeTileflowTreeModel,
|
|
952
|
-
|
|
952
|
+
readTileflowLandmarkTileFootprints,
|
|
953
953
|
serializeTileflowLandmarkRegion,
|
|
954
954
|
tileflowLandmarkTileOf,
|
|
955
|
-
|
|
956
|
-
tileflowModelBundleLimits,
|
|
957
|
-
tileflowModelContentType,
|
|
955
|
+
tileflowModelFileName,
|
|
958
956
|
tileflowTreeModelLimits as tileflowTreeModelLimits2
|
|
959
957
|
} from "@tileflow/core";
|
|
960
958
|
|
|
961
959
|
// src/building-model-mesh.ts
|
|
962
|
-
import {
|
|
960
|
+
import {
|
|
961
|
+
tileflowModelDefaultLightingRoles,
|
|
962
|
+
tileflowModelLightingRoles,
|
|
963
|
+
tileflowModelLimits,
|
|
964
|
+
tileflowModelParts
|
|
965
|
+
} from "@tileflow/core";
|
|
966
|
+
|
|
967
|
+
// src/building-model-materials.ts
|
|
968
|
+
import {
|
|
969
|
+
checkTileflowModelSurface,
|
|
970
|
+
tileflowModelTextureChannels
|
|
971
|
+
} from "@tileflow/core";
|
|
972
|
+
function buildingModelSurface(primitives) {
|
|
973
|
+
if (!primitives.some((primitive) => primitive.surface)) return void 0;
|
|
974
|
+
const attributes = [];
|
|
975
|
+
const materials = [];
|
|
976
|
+
const materialIds = /* @__PURE__ */ new Map();
|
|
977
|
+
const textures = [];
|
|
978
|
+
const textureIds = /* @__PURE__ */ new Map();
|
|
979
|
+
const ranges = [];
|
|
980
|
+
const emitters = [];
|
|
981
|
+
let indexCount = 0;
|
|
982
|
+
for (const primitive of primitives) {
|
|
983
|
+
const count = primitive.positions.length / 3;
|
|
984
|
+
const surface = primitive.surface;
|
|
985
|
+
if (surface) {
|
|
986
|
+
checkTileflowModelSurface(surface, count, primitive.indices.length);
|
|
987
|
+
for (const value of surface.attributes) attributes.push(value);
|
|
988
|
+
emitters.push(...surface.emitters ?? []);
|
|
989
|
+
} else {
|
|
990
|
+
for (let vertex = 0; vertex < count; vertex++) attributes.push(0, 0, 0, 0, 1, 1, 1, 1);
|
|
991
|
+
}
|
|
992
|
+
const remap = (surface?.materials ?? [{}]).map((material) => {
|
|
993
|
+
const copy = { ...material };
|
|
994
|
+
for (const channel of tileflowModelTextureChannels) {
|
|
995
|
+
const ref = copy[channel];
|
|
996
|
+
if (!ref) continue;
|
|
997
|
+
const texture = surface.textures[ref.texture];
|
|
998
|
+
let id2 = textureIds.get(texture);
|
|
999
|
+
if (id2 === void 0) {
|
|
1000
|
+
id2 = textures.length;
|
|
1001
|
+
textures.push(texture);
|
|
1002
|
+
textureIds.set(texture, id2);
|
|
1003
|
+
}
|
|
1004
|
+
copy[channel] = { ...ref, texture: id2 };
|
|
1005
|
+
}
|
|
1006
|
+
const key = JSON.stringify(copy);
|
|
1007
|
+
let id = materialIds.get(key);
|
|
1008
|
+
if (id === void 0) {
|
|
1009
|
+
id = materials.length;
|
|
1010
|
+
materials.push(copy);
|
|
1011
|
+
materialIds.set(key, id);
|
|
1012
|
+
}
|
|
1013
|
+
return id;
|
|
1014
|
+
});
|
|
1015
|
+
const sourceRanges = surface?.ranges ?? [
|
|
1016
|
+
{ from: 0, count: primitive.indices.length, material: 0 }
|
|
1017
|
+
];
|
|
1018
|
+
for (const range of sourceRanges) {
|
|
1019
|
+
const material = remap[range.material];
|
|
1020
|
+
const previous = ranges.at(-1);
|
|
1021
|
+
if (previous?.material === material && JSON.stringify(previous.emissiveBounds) === JSON.stringify(range.emissiveBounds))
|
|
1022
|
+
ranges[ranges.length - 1] = { ...previous, count: previous.count + range.count };
|
|
1023
|
+
else ranges.push({ ...range, from: indexCount + range.from, material });
|
|
1024
|
+
}
|
|
1025
|
+
indexCount += primitive.indices.length;
|
|
1026
|
+
}
|
|
1027
|
+
const result = { attributes, materials, textures, ranges, ...emitters.length ? { emitters } : {} };
|
|
1028
|
+
checkTileflowModelSurface(result, attributes.length / 8, indexCount);
|
|
1029
|
+
return result;
|
|
1030
|
+
}
|
|
1031
|
+
|
|
1032
|
+
// src/building-model-occlusion.ts
|
|
1033
|
+
function buildingModelAmbientVisibility(mesh, { radius = 2, samples = 48 } = {}) {
|
|
1034
|
+
if (!(radius > 0 && Number.isFinite(radius)) || !Number.isInteger(samples) || samples < 1 || samples > 256)
|
|
1035
|
+
throw new RangeError("Model ambient visibility needs a positive radius and 1 to 256 samples.");
|
|
1036
|
+
const { positions, normals, indices } = mesh;
|
|
1037
|
+
const count = indices.length / 3;
|
|
1038
|
+
const triangles2 = new Float64Array(count * 9);
|
|
1039
|
+
const bounds = new Float64Array(count * 6);
|
|
1040
|
+
const centres = new Float64Array(count * 3);
|
|
1041
|
+
const order = Uint32Array.from({ length: count }, (_, i) => i);
|
|
1042
|
+
for (let i = 0; i < count; i += 1) {
|
|
1043
|
+
const a = indices[i * 3] * 3;
|
|
1044
|
+
const b = indices[i * 3 + 1] * 3;
|
|
1045
|
+
const c = indices[i * 3 + 2] * 3;
|
|
1046
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
1047
|
+
const x = positions[a + axis], y = positions[b + axis], z = positions[c + axis];
|
|
1048
|
+
triangles2[i * 9 + axis] = x;
|
|
1049
|
+
triangles2[i * 9 + 3 + axis] = y - x;
|
|
1050
|
+
triangles2[i * 9 + 6 + axis] = z - x;
|
|
1051
|
+
bounds[i * 6 + axis] = Math.min(x, y, z);
|
|
1052
|
+
bounds[i * 6 + 3 + axis] = Math.max(x, y, z);
|
|
1053
|
+
centres[i * 3 + axis] = (x + y + z) / 3;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
const nodes = [];
|
|
1057
|
+
const build = (from, to) => {
|
|
1058
|
+
const box = [Infinity, Infinity, Infinity, -Infinity, -Infinity, -Infinity];
|
|
1059
|
+
for (let at = from; at < to; at += 1) {
|
|
1060
|
+
const i = order[at] * 6;
|
|
1061
|
+
for (let axis2 = 0; axis2 < 3; axis2 += 1) {
|
|
1062
|
+
box[axis2] = Math.min(box[axis2], bounds[i + axis2]);
|
|
1063
|
+
box[axis2 + 3] = Math.max(box[axis2 + 3], bounds[i + axis2 + 3]);
|
|
1064
|
+
}
|
|
1065
|
+
}
|
|
1066
|
+
let axis = 0;
|
|
1067
|
+
for (let next = 1; next < 3; next += 1)
|
|
1068
|
+
if (box[next + 3] - box[next] > box[axis + 3] - box[axis]) axis = next;
|
|
1069
|
+
const index = nodes.length;
|
|
1070
|
+
const node = { bounds: box, from, to, left: -1, right: -1, axis };
|
|
1071
|
+
nodes.push(node);
|
|
1072
|
+
if (to - from > 8) {
|
|
1073
|
+
order.subarray(from, to).sort((a, b) => centres[a * 3 + axis] - centres[b * 3 + axis] || a - b);
|
|
1074
|
+
const middle = from + to >>> 1;
|
|
1075
|
+
node.left = build(from, middle);
|
|
1076
|
+
node.right = build(middle, to);
|
|
1077
|
+
}
|
|
1078
|
+
return index;
|
|
1079
|
+
};
|
|
1080
|
+
build(0, count);
|
|
1081
|
+
const hemisphere = Array.from({ length: samples }, (_, i) => {
|
|
1082
|
+
const r = Math.sqrt((i + 0.5) / samples), angle = i * Math.PI * (3 - Math.sqrt(5));
|
|
1083
|
+
return [r * Math.cos(angle), r * Math.sin(angle), Math.sqrt(1 - r * r)];
|
|
1084
|
+
});
|
|
1085
|
+
const bias = Math.min(2e-3, radius * 1e-3);
|
|
1086
|
+
const stack = new Int32Array(64);
|
|
1087
|
+
const origin = [0, 0, 0];
|
|
1088
|
+
const direction = [0, 0, 0];
|
|
1089
|
+
const nearest = () => {
|
|
1090
|
+
let best = radius, pending = 1;
|
|
1091
|
+
stack[0] = 0;
|
|
1092
|
+
while (pending > 0) {
|
|
1093
|
+
const node = nodes[stack[--pending]];
|
|
1094
|
+
if (!hitsBox(node.bounds, origin, direction, best)) continue;
|
|
1095
|
+
if (node.left >= 0) {
|
|
1096
|
+
const forward = direction[node.axis] >= 0;
|
|
1097
|
+
stack[pending++] = forward ? node.right : node.left;
|
|
1098
|
+
stack[pending++] = forward ? node.left : node.right;
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
for (let at = node.from; at < node.to; at += 1) {
|
|
1102
|
+
const i = order[at] * 9;
|
|
1103
|
+
const e1x = triangles2[i + 3], e1y = triangles2[i + 4], e1z = triangles2[i + 5];
|
|
1104
|
+
const e2x = triangles2[i + 6], e2y = triangles2[i + 7], e2z = triangles2[i + 8];
|
|
1105
|
+
const px = direction[1] * e2z - direction[2] * e2y;
|
|
1106
|
+
const py = direction[2] * e2x - direction[0] * e2z;
|
|
1107
|
+
const pz = direction[0] * e2y - direction[1] * e2x;
|
|
1108
|
+
const determinant2 = e1x * px + e1y * py + e1z * pz;
|
|
1109
|
+
if (Math.abs(determinant2) < 1e-12) continue;
|
|
1110
|
+
const tx = origin[0] - triangles2[i], ty = origin[1] - triangles2[i + 1], tz = origin[2] - triangles2[i + 2];
|
|
1111
|
+
const inverse = 1 / determinant2;
|
|
1112
|
+
const u = (tx * px + ty * py + tz * pz) * inverse;
|
|
1113
|
+
if (u < 0 || u > 1) continue;
|
|
1114
|
+
const qx = ty * e1z - tz * e1y, qy = tz * e1x - tx * e1z, qz = tx * e1y - ty * e1x;
|
|
1115
|
+
const v = (direction[0] * qx + direction[1] * qy + direction[2] * qz) * inverse;
|
|
1116
|
+
if (v < 0 || u + v > 1) continue;
|
|
1117
|
+
const distance = (e2x * qx + e2y * qy + e2z * qz) * inverse;
|
|
1118
|
+
if (distance > bias && distance < best) best = distance;
|
|
1119
|
+
}
|
|
1120
|
+
}
|
|
1121
|
+
return best;
|
|
1122
|
+
};
|
|
1123
|
+
const values = new Array(positions.length / 3);
|
|
1124
|
+
const cache = /* @__PURE__ */ new Map();
|
|
1125
|
+
for (let vertex = 0; vertex < values.length; vertex += 1) {
|
|
1126
|
+
const at = vertex * 3;
|
|
1127
|
+
const nx = normals[at], ny = normals[at + 1], nz = normals[at + 2];
|
|
1128
|
+
const point3 = [positions[at], positions[at + 1], positions[at + 2]];
|
|
1129
|
+
const key = [...point3, nx, ny, nz].map((v) => Math.round(v * 1e5)).join(",");
|
|
1130
|
+
const cached = cache.get(key);
|
|
1131
|
+
if (cached !== void 0) {
|
|
1132
|
+
values[vertex] = cached;
|
|
1133
|
+
continue;
|
|
1134
|
+
}
|
|
1135
|
+
const tangent = Math.abs(nz) < 0.9 ? [-ny, nx, 0] : [nz, 0, -nx];
|
|
1136
|
+
const length = Math.hypot(...tangent);
|
|
1137
|
+
if (length === 0) {
|
|
1138
|
+
values[vertex] = 1;
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1141
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
1142
|
+
tangent[axis] /= length;
|
|
1143
|
+
origin[axis] = point3[axis] + normals[at + axis] * bias;
|
|
1144
|
+
}
|
|
1145
|
+
const bitangent = [
|
|
1146
|
+
ny * tangent[2] - nz * tangent[1],
|
|
1147
|
+
nz * tangent[0] - nx * tangent[2],
|
|
1148
|
+
nx * tangent[1] - ny * tangent[0]
|
|
1149
|
+
];
|
|
1150
|
+
let visibility = 0;
|
|
1151
|
+
for (const sample of hemisphere) {
|
|
1152
|
+
for (let axis = 0; axis < 3; axis += 1)
|
|
1153
|
+
direction[axis] = sample[0] * tangent[axis] + sample[1] * bitangent[axis] + sample[2] * normals[at + axis];
|
|
1154
|
+
visibility += nearest() / radius;
|
|
1155
|
+
}
|
|
1156
|
+
values[vertex] = visibility / samples;
|
|
1157
|
+
cache.set(key, values[vertex]);
|
|
1158
|
+
}
|
|
1159
|
+
return values;
|
|
1160
|
+
}
|
|
1161
|
+
function hitsBox(box, origin, direction, limit) {
|
|
1162
|
+
let low = 0, high = limit;
|
|
1163
|
+
for (let axis = 0; axis < 3; axis += 1) {
|
|
1164
|
+
const o = origin[axis], d = direction[axis];
|
|
1165
|
+
if (Math.abs(d) < 1e-12) {
|
|
1166
|
+
if (o < box[axis] || o > box[axis + 3]) return false;
|
|
1167
|
+
continue;
|
|
1168
|
+
}
|
|
1169
|
+
const a = (box[axis] - o) / d, b = (box[axis + 3] - o) / d;
|
|
1170
|
+
low = Math.max(low, Math.min(a, b));
|
|
1171
|
+
high = Math.min(high, Math.max(a, b));
|
|
1172
|
+
if (low > high) return false;
|
|
1173
|
+
}
|
|
1174
|
+
return true;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
// src/gltf-materials.ts
|
|
1178
|
+
import {
|
|
1179
|
+
checkTileflowModelSurface as checkTileflowModelSurface2,
|
|
1180
|
+
tileflowModelImageSize,
|
|
1181
|
+
tileflowModelSurfaceLimits
|
|
1182
|
+
} from "@tileflow/core";
|
|
1183
|
+
function createGltfMaterialReader(json, bin, accessor2) {
|
|
1184
|
+
const textures = [];
|
|
1185
|
+
const textureIds = /* @__PURE__ */ new Map();
|
|
1186
|
+
const imageCache = /* @__PURE__ */ new Map();
|
|
1187
|
+
function imageAt(index) {
|
|
1188
|
+
if (!Number.isInteger(index)) throw new RangeError("A material names a missing image.");
|
|
1189
|
+
const cached = imageCache.get(index);
|
|
1190
|
+
if (cached) return cached;
|
|
1191
|
+
const image = record(list(json.images)[index]);
|
|
1192
|
+
let bytes;
|
|
1193
|
+
let mimeType = image.mimeType;
|
|
1194
|
+
if (typeof image.uri === "string") {
|
|
1195
|
+
const match = /^data:(image\/(?:png|jpeg));base64,([A-Za-z0-9+/=\r\n]+)$/.exec(image.uri);
|
|
1196
|
+
if (!match || image.uri.length > 24 * 1024 * 1024)
|
|
1197
|
+
throw new RangeError("Embed PNG/JPEG images in the GLB; external images are not read.");
|
|
1198
|
+
if (mimeType !== void 0 && mimeType !== match[1])
|
|
1199
|
+
throw new RangeError("An image has conflicting media types.");
|
|
1200
|
+
mimeType = match[1];
|
|
1201
|
+
bytes = new Uint8Array(Buffer.from(match[2], "base64"));
|
|
1202
|
+
} else {
|
|
1203
|
+
if (!Number.isInteger(image.bufferView)) throw new RangeError("Embed each image in the GLB.");
|
|
1204
|
+
const view = record(list(json.bufferViews)[image.bufferView]);
|
|
1205
|
+
const start = view.byteOffset ?? 0;
|
|
1206
|
+
const length = view.byteLength;
|
|
1207
|
+
if (!bin || (view.buffer ?? 0) !== 0 || !Number.isInteger(start) || !Number.isInteger(length) || start < 0 || length <= 0 || start + length > bin.length)
|
|
1208
|
+
throw new RangeError("An embedded image extends outside the GLB.");
|
|
1209
|
+
bytes = bin.subarray(start, start + length);
|
|
1210
|
+
}
|
|
1211
|
+
if (mimeType !== "image/png" && mimeType !== "image/jpeg")
|
|
1212
|
+
throw new RangeError("Use an embedded PNG or JPEG image.");
|
|
1213
|
+
const size = tileflowModelImageSize(bytes, mimeType);
|
|
1214
|
+
if (!size || Math.max(...size) > tileflowModelSurfaceLimits.textureDimension || size[0] * size[1] > tileflowModelSurfaceLimits.texturePixels)
|
|
1215
|
+
throw new RangeError("The model image is invalid or exceeds its pixel budget.");
|
|
1216
|
+
const result = {
|
|
1217
|
+
bytes,
|
|
1218
|
+
width: size[0],
|
|
1219
|
+
height: size[1],
|
|
1220
|
+
mimeType
|
|
1221
|
+
};
|
|
1222
|
+
imageCache.set(index, result);
|
|
1223
|
+
return result;
|
|
1224
|
+
}
|
|
1225
|
+
function textureAt(index) {
|
|
1226
|
+
if (!Number.isInteger(index)) throw new RangeError("A material names a missing texture.");
|
|
1227
|
+
const cached = textureIds.get(index);
|
|
1228
|
+
if (cached !== void 0) return cached;
|
|
1229
|
+
const source = record(list(json.textures)[index]);
|
|
1230
|
+
const image = imageAt(source.source);
|
|
1231
|
+
const sampler = source.sampler === void 0 ? {} : record(list(json.samplers)[source.sampler]);
|
|
1232
|
+
if (source.sampler !== void 0 && (!Number.isInteger(source.sampler) || !list(json.samplers)[source.sampler]))
|
|
1233
|
+
throw new RangeError("A texture names a missing sampler.");
|
|
1234
|
+
const id = textures.length;
|
|
1235
|
+
if (id >= tileflowModelSurfaceLimits.textures)
|
|
1236
|
+
throw new RangeError("The model has too many textures.");
|
|
1237
|
+
textures.push({
|
|
1238
|
+
...image,
|
|
1239
|
+
...pick(sampler, ["wrapS", "wrapT", "minFilter", "magFilter"])
|
|
1240
|
+
});
|
|
1241
|
+
textureIds.set(index, id);
|
|
1242
|
+
return id;
|
|
1243
|
+
}
|
|
1244
|
+
function textureRef(value, normal = false) {
|
|
1245
|
+
if (value === void 0) return void 0;
|
|
1246
|
+
const ref = record(value);
|
|
1247
|
+
const transform3 = record(record(ref.extensions).KHR_texture_transform);
|
|
1248
|
+
const texCoord = transform3.texCoord ?? ref.texCoord;
|
|
1249
|
+
return {
|
|
1250
|
+
texture: textureAt(ref.index),
|
|
1251
|
+
...texCoord === void 0 ? {} : { texCoord },
|
|
1252
|
+
...pick(transform3, ["offset", "scale", "rotation"]),
|
|
1253
|
+
...normal && ref.scale !== void 0 ? { strength: ref.scale } : {},
|
|
1254
|
+
...!normal && ref.strength !== void 0 ? { strength: ref.strength } : {}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
return (primitive, count, indexCount) => {
|
|
1258
|
+
const source = primitive.material === void 0 ? {} : record(list(json.materials)[primitive.material]);
|
|
1259
|
+
if (primitive.material !== void 0 && (!Number.isInteger(primitive.material) || !list(json.materials)[primitive.material]))
|
|
1260
|
+
throw new RangeError("A primitive names a missing material.");
|
|
1261
|
+
const pbr = record(source.pbrMetallicRoughness);
|
|
1262
|
+
const extensions = record(source.extensions);
|
|
1263
|
+
const material = {
|
|
1264
|
+
...pick(pbr, ["baseColorFactor", "metallicFactor", "roughnessFactor"]),
|
|
1265
|
+
...pick(source, ["emissiveFactor", "alphaMode", "alphaCutoff", "doubleSided"]),
|
|
1266
|
+
...extensions.KHR_materials_unlit === void 0 ? {} : { unlit: true },
|
|
1267
|
+
...pick(record(extensions.KHR_materials_emissive_strength), ["emissiveStrength"]),
|
|
1268
|
+
...optional("baseColorTexture", textureRef(pbr.baseColorTexture)),
|
|
1269
|
+
...optional("metallicRoughnessTexture", textureRef(pbr.metallicRoughnessTexture)),
|
|
1270
|
+
...optional("normalTexture", textureRef(source.normalTexture, true)),
|
|
1271
|
+
...optional("occlusionTexture", textureRef(source.occlusionTexture)),
|
|
1272
|
+
...optional("emissiveTexture", textureRef(source.emissiveTexture))
|
|
1273
|
+
};
|
|
1274
|
+
const attributes = record(primitive.attributes);
|
|
1275
|
+
const read = (name, size) => {
|
|
1276
|
+
const index = attributes[name];
|
|
1277
|
+
if (index === void 0) return void 0;
|
|
1278
|
+
if (!Number.isInteger(index)) throw new RangeError(`Invalid ${name} accessor.`);
|
|
1279
|
+
const value = accessor2(index);
|
|
1280
|
+
if (!size.includes(value.size) || value.values.length !== count * value.size)
|
|
1281
|
+
throw new RangeError(`${name} must have one value per vertex.`);
|
|
1282
|
+
return value;
|
|
1283
|
+
};
|
|
1284
|
+
const uv0 = read("TEXCOORD_0", [2]), uv1 = read("TEXCOORD_1", [2]);
|
|
1285
|
+
const colour = read("COLOR_0", [3, 4]);
|
|
1286
|
+
for (const key of [
|
|
1287
|
+
"baseColorTexture",
|
|
1288
|
+
"metallicRoughnessTexture",
|
|
1289
|
+
"normalTexture",
|
|
1290
|
+
"occlusionTexture",
|
|
1291
|
+
"emissiveTexture"
|
|
1292
|
+
]) {
|
|
1293
|
+
const ref = material[key];
|
|
1294
|
+
if (ref && !(ref.texCoord === 1 ? uv1 : ref.texCoord === void 0 || ref.texCoord === 0 ? uv0 : void 0))
|
|
1295
|
+
throw new RangeError(`The ${key} needs its declared UV coordinates.`);
|
|
1296
|
+
}
|
|
1297
|
+
const data = new Float32Array(count * 8);
|
|
1298
|
+
for (let vertex = 0; vertex < count; vertex++) {
|
|
1299
|
+
for (let axis = 0; axis < 2; axis++) {
|
|
1300
|
+
data[vertex * 8 + axis] = uv0?.values[vertex * 2 + axis] ?? 0;
|
|
1301
|
+
data[vertex * 8 + 2 + axis] = uv1?.values[vertex * 2 + axis] ?? 0;
|
|
1302
|
+
}
|
|
1303
|
+
for (let channel = 0; channel < 4; channel++)
|
|
1304
|
+
data[vertex * 8 + 4 + channel] = colour && channel < colour.size ? colour.values[vertex * colour.size + channel] : 1;
|
|
1305
|
+
}
|
|
1306
|
+
const surface = {
|
|
1307
|
+
attributes: data,
|
|
1308
|
+
materials: [material],
|
|
1309
|
+
textures,
|
|
1310
|
+
ranges: [{ from: 0, count: indexCount, material: 0 }]
|
|
1311
|
+
};
|
|
1312
|
+
checkTileflowModelSurface2(surface, count, indexCount);
|
|
1313
|
+
return surface;
|
|
1314
|
+
};
|
|
1315
|
+
}
|
|
1316
|
+
function record(value) {
|
|
1317
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
1318
|
+
}
|
|
1319
|
+
function list(value) {
|
|
1320
|
+
return Array.isArray(value) ? value : [];
|
|
1321
|
+
}
|
|
1322
|
+
function pick(value, keys) {
|
|
1323
|
+
return Object.fromEntries(
|
|
1324
|
+
keys.filter((key) => value[key] !== void 0).map((key) => [key, value[key]])
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
function optional(key, value) {
|
|
1328
|
+
return value === void 0 ? {} : { [key]: value };
|
|
1329
|
+
}
|
|
963
1330
|
|
|
964
1331
|
// src/gltf-reader.ts
|
|
965
1332
|
var TileflowGltfError = class extends Error {
|
|
@@ -984,7 +1351,7 @@ var componentBytes = {
|
|
|
984
1351
|
5126: 4
|
|
985
1352
|
};
|
|
986
1353
|
var typeSizes = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4 };
|
|
987
|
-
function readTileflowGltfTriangles(bytes) {
|
|
1354
|
+
function readTileflowGltfTriangles(bytes, { materials = false } = {}) {
|
|
988
1355
|
const { json, bin } = chunks(bytes);
|
|
989
1356
|
const required = array(json.extensionsRequired).filter((name) => typeof name === "string");
|
|
990
1357
|
const compressed = required.find((name) => compressions.has(name));
|
|
@@ -993,24 +1360,57 @@ function readTileflowGltfTriangles(bytes) {
|
|
|
993
1360
|
`uses compressed geometry (${String(compressed)}); export it without compression`
|
|
994
1361
|
);
|
|
995
1362
|
for (const buffer of array(json.buffers))
|
|
996
|
-
if (
|
|
1363
|
+
if (record2(buffer).uri !== void 0)
|
|
997
1364
|
throw new TileflowGltfError("keeps its geometry in another file; export a single .glb");
|
|
998
|
-
const nodes = array(json.nodes).map(
|
|
999
|
-
const scenes = array(json.scenes).map(
|
|
1365
|
+
const nodes = array(json.nodes).map(record2);
|
|
1366
|
+
const scenes = array(json.scenes).map(record2);
|
|
1000
1367
|
const scene = scenes[typeof json.scene === "number" ? json.scene : 0];
|
|
1001
1368
|
const roots = scene ? array(scene.nodes) : nodes.map((_, index) => index).filter((index) => !nodes.some((node) => array(node.children).includes(index)));
|
|
1002
1369
|
const out = [];
|
|
1370
|
+
const physical = materials ? createGltfMaterialReader(json, bin, (index) => accessorWithSize(json, bin, index)) : void 0;
|
|
1371
|
+
const emitters = [];
|
|
1003
1372
|
const visit = (index, parent, depth) => {
|
|
1004
1373
|
if (typeof index !== "number" || !nodes[index])
|
|
1005
1374
|
throw new TileflowGltfError("names a node it does not hold");
|
|
1006
1375
|
if (depth > 64) throw new TileflowGltfError("nests its nodes more than 64 deep");
|
|
1007
1376
|
const node = nodes[index];
|
|
1008
1377
|
const world = multiply(parent, local(node));
|
|
1009
|
-
if (
|
|
1378
|
+
if (physical) {
|
|
1379
|
+
const lights = record2(record2(node.extras).tileflow).emitters;
|
|
1380
|
+
if (lights !== void 0 && !Array.isArray(lights))
|
|
1381
|
+
throw new TileflowGltfError("area emitters must be an array");
|
|
1382
|
+
for (const value of array(lights)) {
|
|
1383
|
+
const own = record2(value);
|
|
1384
|
+
const point3 = (key) => {
|
|
1385
|
+
const input = array(own[key]);
|
|
1386
|
+
if (input.length !== 3 || !input.every((value2) => typeof value2 === "number" && Number.isFinite(value2)))
|
|
1387
|
+
throw new TileflowGltfError("an area emitter needs finite three-dimensional endpoints");
|
|
1388
|
+
const p = transform2(world, input[0], input[1], input[2], 1);
|
|
1389
|
+
return [p[0], -p[2], p[1]];
|
|
1390
|
+
};
|
|
1391
|
+
const heightScale = Math.hypot(...transform2(world, 0, 1, 0, 0));
|
|
1392
|
+
const depthScale = Math.hypot(...transform2(world, 0, 0, 1, 0));
|
|
1393
|
+
if (typeof own.height !== "number")
|
|
1394
|
+
throw new TileflowGltfError("an area emitter needs its height");
|
|
1395
|
+
emitters.push({
|
|
1396
|
+
start: point3("start"),
|
|
1397
|
+
end: point3("end"),
|
|
1398
|
+
height: own.height * heightScale,
|
|
1399
|
+
...own.depth === void 0 ? {} : { depth: Number(own.depth) * depthScale },
|
|
1400
|
+
role: own.role,
|
|
1401
|
+
part: own.part
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
if (typeof node.mesh === "number") out.push(...meshOf(json, bin, node.mesh, world, physical));
|
|
1010
1406
|
for (const child of array(node.children)) visit(child, world, depth + 1);
|
|
1011
1407
|
};
|
|
1012
1408
|
for (const root of roots) visit(root, identity(), 0);
|
|
1013
1409
|
if (out.length === 0) throw new TileflowGltfError("holds no triangles in its scene");
|
|
1410
|
+
if (emitters.length) {
|
|
1411
|
+
const first = out.find((part) => !/^footprint(?:\.\d+)?$/i.test(part.material ?? ""));
|
|
1412
|
+
if (first?.surface) first.surface = { ...first.surface, emitters };
|
|
1413
|
+
}
|
|
1014
1414
|
return out;
|
|
1015
1415
|
}
|
|
1016
1416
|
function chunks(bytes) {
|
|
@@ -1031,7 +1431,7 @@ function chunks(bytes) {
|
|
|
1031
1431
|
const body = bytes.subarray(start, start + length);
|
|
1032
1432
|
if (type === jsonChunk && json === void 0) {
|
|
1033
1433
|
try {
|
|
1034
|
-
json =
|
|
1434
|
+
json = record2(JSON.parse(new TextDecoder().decode(body)));
|
|
1035
1435
|
} catch {
|
|
1036
1436
|
throw new TileflowGltfError("has an unreadable JSON chunk");
|
|
1037
1437
|
}
|
|
@@ -1041,14 +1441,14 @@ function chunks(bytes) {
|
|
|
1041
1441
|
if (!json) throw new TileflowGltfError("has no JSON chunk");
|
|
1042
1442
|
return { json, bin };
|
|
1043
1443
|
}
|
|
1044
|
-
function meshOf(json, bin, index, world) {
|
|
1045
|
-
const mesh =
|
|
1444
|
+
function meshOf(json, bin, index, world, physical) {
|
|
1445
|
+
const mesh = record2(array(json.meshes)[index]);
|
|
1046
1446
|
const normalMatrix = inverseTranspose(world);
|
|
1047
|
-
|
|
1048
|
-
const primitive =
|
|
1447
|
+
const result = array(mesh.primitives).map((value) => {
|
|
1448
|
+
const primitive = record2(value);
|
|
1049
1449
|
if ((primitive.mode ?? triangles) !== triangles)
|
|
1050
1450
|
throw new TileflowGltfError("draws points or lines; a tree model draws triangles");
|
|
1051
|
-
const attributes =
|
|
1451
|
+
const attributes = record2(primitive.attributes);
|
|
1052
1452
|
if (typeof attributes.POSITION !== "number")
|
|
1053
1453
|
throw new TileflowGltfError("has a primitive without positions");
|
|
1054
1454
|
const raw = accessor(json, bin, attributes.POSITION, 3);
|
|
@@ -1080,8 +1480,8 @@ function meshOf(json, bin, index, world) {
|
|
|
1080
1480
|
normals.push(x / size, -z / size, y / size);
|
|
1081
1481
|
}
|
|
1082
1482
|
}
|
|
1083
|
-
const material = typeof primitive.material === "number" ?
|
|
1084
|
-
const base = array(
|
|
1483
|
+
const material = typeof primitive.material === "number" ? record2(array(json.materials)[primitive.material]) : void 0;
|
|
1484
|
+
const base = array(record2(material?.pbrMetallicRoughness).baseColorFactor).map(Number);
|
|
1085
1485
|
const factor = base.length >= 3 && base.every(Number.isFinite) ? base : [1, 1, 1];
|
|
1086
1486
|
const colours = [];
|
|
1087
1487
|
const vertexColours = typeof attributes.COLOR_0 === "number" ? accessorWithSize(json, bin, attributes.COLOR_0) : void 0;
|
|
@@ -1098,12 +1498,37 @@ function meshOf(json, bin, index, world) {
|
|
|
1098
1498
|
[indices[at + 1], indices[at + 2]] = [indices[at + 2], indices[at + 1]];
|
|
1099
1499
|
return {
|
|
1100
1500
|
material: typeof material?.name === "string" ? material.name : void 0,
|
|
1501
|
+
...record2(record2(material?.extras).tileflow).lightingRole === void 0 ? {} : {
|
|
1502
|
+
lightingRole: record2(record2(material?.extras).tileflow).lightingRole
|
|
1503
|
+
},
|
|
1101
1504
|
positions,
|
|
1102
1505
|
normals,
|
|
1103
1506
|
colours,
|
|
1104
|
-
indices
|
|
1507
|
+
indices,
|
|
1508
|
+
...physical ? { surface: physical(primitive, count, indices.length) } : {}
|
|
1105
1509
|
};
|
|
1106
1510
|
});
|
|
1511
|
+
if (physical) {
|
|
1512
|
+
let low = Infinity, high = -Infinity;
|
|
1513
|
+
for (const part of result)
|
|
1514
|
+
if (!/^footprint(?:\.\d+)?$/i.test(part.material ?? ""))
|
|
1515
|
+
for (let at = 2; at < part.positions.length; at += 3) {
|
|
1516
|
+
low = Math.min(low, part.positions[at]);
|
|
1517
|
+
high = Math.max(high, part.positions[at]);
|
|
1518
|
+
}
|
|
1519
|
+
if (Number.isFinite(low)) {
|
|
1520
|
+
for (const part of result)
|
|
1521
|
+
if (part.surface)
|
|
1522
|
+
part.surface = {
|
|
1523
|
+
...part.surface,
|
|
1524
|
+
ranges: part.surface.ranges.map((range) => ({
|
|
1525
|
+
...range,
|
|
1526
|
+
emissiveBounds: [low, high]
|
|
1527
|
+
}))
|
|
1528
|
+
};
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
return result;
|
|
1107
1532
|
}
|
|
1108
1533
|
function accessor(json, bin, index, size) {
|
|
1109
1534
|
const read = accessorWithSize(json, bin, index);
|
|
@@ -1112,7 +1537,7 @@ function accessor(json, bin, index, size) {
|
|
|
1112
1537
|
return read.values;
|
|
1113
1538
|
}
|
|
1114
1539
|
function accessorWithSize(json, bin, index) {
|
|
1115
|
-
const accessor2 =
|
|
1540
|
+
const accessor2 = record2(array(json.accessors)[index]);
|
|
1116
1541
|
if (accessor2.sparse !== void 0)
|
|
1117
1542
|
throw new TileflowGltfError("uses sparse accessors; export without them");
|
|
1118
1543
|
const size = typeSizes[String(accessor2.type)];
|
|
@@ -1123,7 +1548,7 @@ function accessorWithSize(json, bin, index) {
|
|
|
1123
1548
|
throw new TileflowGltfError("has an accessor Tileflow cannot read");
|
|
1124
1549
|
if (typeof accessor2.bufferView !== "number")
|
|
1125
1550
|
return { values: new Array(count * size).fill(0), size };
|
|
1126
|
-
const view =
|
|
1551
|
+
const view = record2(array(json.bufferViews)[accessor2.bufferView]);
|
|
1127
1552
|
if (!bin || Number(view.buffer ?? 0) !== 0)
|
|
1128
1553
|
throw new TileflowGltfError("has geometry outside its binary chunk");
|
|
1129
1554
|
const stride = Number(view.byteStride ?? 0) || size * bytesEach;
|
|
@@ -1245,15 +1670,15 @@ function inverseTranspose(m) {
|
|
|
1245
1670
|
1
|
|
1246
1671
|
];
|
|
1247
1672
|
}
|
|
1248
|
-
function
|
|
1673
|
+
function record2(value) {
|
|
1249
1674
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : {};
|
|
1250
1675
|
}
|
|
1251
1676
|
function array(value) {
|
|
1252
1677
|
return Array.isArray(value) ? value : [];
|
|
1253
1678
|
}
|
|
1254
1679
|
function vector(value, fallback) {
|
|
1255
|
-
const
|
|
1256
|
-
return
|
|
1680
|
+
const list2 = array(value).map(Number);
|
|
1681
|
+
return list2.length === fallback.length && list2.every(Number.isFinite) ? list2 : fallback;
|
|
1257
1682
|
}
|
|
1258
1683
|
|
|
1259
1684
|
// src/building-model-mesh.ts
|
|
@@ -1273,25 +1698,33 @@ function tileflowBuildingModelDeclaredFootprint(primitives) {
|
|
|
1273
1698
|
}
|
|
1274
1699
|
return indices.length > 0 ? { positions, indices } : void 0;
|
|
1275
1700
|
}
|
|
1276
|
-
function prepareTileflowBuildingModelMesh(bytes) {
|
|
1277
|
-
const primitives = bytes instanceof Uint8Array ? readTileflowGltfTriangles(bytes) : bytes;
|
|
1701
|
+
function prepareTileflowBuildingModelMesh(bytes, { ambientOcclusion = true } = {}) {
|
|
1702
|
+
const primitives = bytes instanceof Uint8Array ? readTileflowGltfTriangles(bytes, { materials: true }) : bytes;
|
|
1703
|
+
const drawn = primitives.filter((primitive) => !isFootprint(primitive));
|
|
1704
|
+
const surface = buildingModelSurface(drawn);
|
|
1278
1705
|
const positions = [];
|
|
1279
1706
|
const normals = [];
|
|
1280
1707
|
const parts = [];
|
|
1708
|
+
const lightingRoles = [];
|
|
1281
1709
|
const luminance = [];
|
|
1282
1710
|
const indices = [];
|
|
1283
|
-
for (const primitive of
|
|
1284
|
-
if (isFootprint(primitive)) continue;
|
|
1711
|
+
for (const primitive of drawn) {
|
|
1285
1712
|
const part = partOf(primitive.material);
|
|
1286
1713
|
if (part === void 0)
|
|
1287
1714
|
throw new TileflowGltfError(
|
|
1288
1715
|
primitive.material === void 0 ? "has a mesh without a material; name each material surface, secondary, glass, accent or roof" : `has a material "${primitive.material}" that names no part of a building; name each material surface, secondary, glass, accent or roof`
|
|
1289
1716
|
);
|
|
1290
1717
|
const offset = positions.length / 3;
|
|
1718
|
+
const role = primitive.lightingRole === void 0 ? tileflowModelDefaultLightingRoles[part] : typeof primitive.lightingRole === "string" && Object.hasOwn(tileflowModelLightingRoles, primitive.lightingRole) ? tileflowModelLightingRoles[primitive.lightingRole] : void 0;
|
|
1719
|
+
if (role === void 0)
|
|
1720
|
+
throw new TileflowGltfError(
|
|
1721
|
+
"has an unknown lighting role; use unclassified, wall, door, roof, window, lamp or logo"
|
|
1722
|
+
);
|
|
1291
1723
|
const count2 = primitive.positions.length / 3;
|
|
1292
1724
|
positions.push(...primitive.positions);
|
|
1293
1725
|
for (let vertex = 0; vertex < count2; vertex += 1) {
|
|
1294
1726
|
parts.push(part);
|
|
1727
|
+
lightingRoles.push(role);
|
|
1295
1728
|
normals.push(primitive.normals?.slice(vertex * 3, vertex * 3 + 3));
|
|
1296
1729
|
const [r, g, b] = primitive.colours.slice(vertex * 3, vertex * 3 + 3);
|
|
1297
1730
|
luminance.push(0.2126 * r + 0.7152 * g + 0.0722 * b);
|
|
@@ -1347,8 +1780,10 @@ function prepareTileflowBuildingModelMesh(bytes) {
|
|
|
1347
1780
|
positions,
|
|
1348
1781
|
normals: outNormals,
|
|
1349
1782
|
parts,
|
|
1783
|
+
lightingRoles,
|
|
1350
1784
|
shades,
|
|
1351
|
-
|
|
1785
|
+
...surface ? { surface } : {},
|
|
1786
|
+
occlusion: ambientOcclusion ? buildingModelAmbientVisibility({ positions, normals: outNormals, indices }) : new Array(count).fill(1),
|
|
1352
1787
|
indices
|
|
1353
1788
|
};
|
|
1354
1789
|
}
|
|
@@ -1426,9 +1861,9 @@ function tileflowBuildingModelFootprint(mesh) {
|
|
|
1426
1861
|
const next = /* @__PURE__ */ new Map();
|
|
1427
1862
|
const add = (from, to) => {
|
|
1428
1863
|
const key = `${from[0]},${from[1]}`;
|
|
1429
|
-
const
|
|
1430
|
-
|
|
1431
|
-
next.set(key,
|
|
1864
|
+
const list2 = next.get(key) ?? [];
|
|
1865
|
+
list2.push(to);
|
|
1866
|
+
next.set(key, list2);
|
|
1432
1867
|
};
|
|
1433
1868
|
for (let y = 0; y < rows; y += 1)
|
|
1434
1869
|
for (let x = 0; x < columns; x += 1) {
|
|
@@ -1520,8 +1955,8 @@ function ringArea(ring) {
|
|
|
1520
1955
|
function edge(a, b, p) {
|
|
1521
1956
|
return (b[0] - a[0]) * (p[1] - a[1]) - (b[1] - a[1]) * (p[0] - a[0]);
|
|
1522
1957
|
}
|
|
1523
|
-
function point(
|
|
1524
|
-
return [
|
|
1958
|
+
function point(list2, vertex) {
|
|
1959
|
+
return [list2[vertex * 3], list2[vertex * 3 + 1], list2[vertex * 3 + 2]];
|
|
1525
1960
|
}
|
|
1526
1961
|
function sub(a, b) {
|
|
1527
1962
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
@@ -1657,8 +2092,8 @@ function partOf2(material) {
|
|
|
1657
2092
|
function isCrown(part) {
|
|
1658
2093
|
return part === tileflowTreeParts.leaves || part === tileflowTreeParts.fronds;
|
|
1659
2094
|
}
|
|
1660
|
-
function point2(
|
|
1661
|
-
return [
|
|
2095
|
+
function point2(list2, vertex) {
|
|
2096
|
+
return [list2[vertex * 3], list2[vertex * 3 + 1], list2[vertex * 3 + 2]];
|
|
1662
2097
|
}
|
|
1663
2098
|
function sub2(a, b) {
|
|
1664
2099
|
return [a[0] - b[0], a[1] - b[1], a[2] - b[2]];
|
|
@@ -1727,14 +2162,16 @@ async function packageTileflowLandmarkTile(landmarks) {
|
|
|
1727
2162
|
}
|
|
1728
2163
|
await MeshoptEncoder.ready;
|
|
1729
2164
|
const bytes = encodeTileflowModels(models, MeshoptEncoder);
|
|
1730
|
-
|
|
2165
|
+
const footprints = readTileflowLandmarkTileFootprints(bytes);
|
|
2166
|
+
if (!footprints) throw new Error("The packaged landmark tile cannot be read back.");
|
|
2167
|
+
return { x: tile[0], y: tile[1], bytes, sha256: sha256Of(bytes), landmarks: footprints };
|
|
1731
2168
|
}
|
|
1732
2169
|
function encodeTileflowLandmarkRegion(region) {
|
|
1733
2170
|
const bytes = serializeTileflowLandmarkRegion(region);
|
|
1734
2171
|
return { bytes, sha256: sha256Of(bytes) };
|
|
1735
2172
|
}
|
|
1736
2173
|
function buildingModelEntry(id, levels) {
|
|
1737
|
-
const primitives = readTileflowGltfTriangles(levels.near);
|
|
2174
|
+
const primitives = readTileflowGltfTriangles(levels.near, { materials: true });
|
|
1738
2175
|
if (!isTileflowBuildingModel(primitives))
|
|
1739
2176
|
throw new TileflowGltfError(
|
|
1740
2177
|
"is not a building model; name each material surface, secondary, glass, accent or roof"
|
|
@@ -1750,34 +2187,6 @@ function buildingModelEntry(id, levels) {
|
|
|
1750
2187
|
)
|
|
1751
2188
|
};
|
|
1752
2189
|
}
|
|
1753
|
-
async function createTileflowModelBundle(buildings) {
|
|
1754
|
-
const entries = Object.entries(buildings).sort(([a], [b]) => compareCodeUnits4(a, b));
|
|
1755
|
-
if (entries.length === 0) return void 0;
|
|
1756
|
-
const totalBytes = entries.reduce((total, [, model]) => total + model.bytes.byteLength, 0);
|
|
1757
|
-
if (totalBytes > tileflowModelBundleLimits.maxPackageBytes)
|
|
1758
|
-
throw new RangeError(
|
|
1759
|
-
`The map's building models together are ${totalBytes} bytes once prepared; a hosted map takes at most ${tileflowModelBundleLimits.maxPackageBytes}. Simplify or remove models.`
|
|
1760
|
-
);
|
|
1761
|
-
const manifest = {
|
|
1762
|
-
format: "tileflow-model-bundle-v1",
|
|
1763
|
-
models: entries.map(([id, model]) => ({
|
|
1764
|
-
byteLength: model.bytes.byteLength,
|
|
1765
|
-
contentType: tileflowModelContentType,
|
|
1766
|
-
file: model.fileName,
|
|
1767
|
-
id,
|
|
1768
|
-
sha256: model.sha256
|
|
1769
|
-
}))
|
|
1770
|
-
};
|
|
1771
|
-
return {
|
|
1772
|
-
contentHash: await hashTileflowModelBundleManifest(manifest),
|
|
1773
|
-
manifest,
|
|
1774
|
-
files: entries.map(([, model]) => ({
|
|
1775
|
-
contentType: tileflowModelContentType,
|
|
1776
|
-
fileName: model.fileName,
|
|
1777
|
-
source: model.bytes
|
|
1778
|
-
}))
|
|
1779
|
-
};
|
|
1780
|
-
}
|
|
1781
2190
|
function sha256Of(bytes) {
|
|
1782
2191
|
return createHash("sha256").update(bytes).digest("hex");
|
|
1783
2192
|
}
|
|
@@ -1858,6 +2267,7 @@ async function prepareTileflowMapTreeModels(directories, options) {
|
|
|
1858
2267
|
}
|
|
1859
2268
|
if (isTileflowBuildingModel(primitives)) {
|
|
1860
2269
|
const files2 = { near: nearBytes };
|
|
2270
|
+
const sources = [];
|
|
1861
2271
|
let failing = entry.near;
|
|
1862
2272
|
try {
|
|
1863
2273
|
for (const level of ["near", "mid", "far"]) {
|
|
@@ -1866,15 +2276,18 @@ async function prepareTileflowMapTreeModels(directories, options) {
|
|
|
1866
2276
|
failing = file;
|
|
1867
2277
|
const bytes = level === "near" ? nearBytes : new Uint8Array(await readFile(file.path));
|
|
1868
2278
|
files2[level] = bytes;
|
|
1869
|
-
if (level !== "near") prepareTileflowBuildingModelMesh(bytes);
|
|
1870
|
-
|
|
2279
|
+
if (level !== "near") prepareTileflowBuildingModelMesh(bytes, { ambientOcclusion: false });
|
|
2280
|
+
const sha256 = createHash("sha256").update(bytes).digest("hex");
|
|
2281
|
+
identities.push({ id, level, sha256 });
|
|
2282
|
+
sources.push({ level, sha256, byteLength: bytes.byteLength });
|
|
1871
2283
|
}
|
|
1872
2284
|
failing = entry.near;
|
|
1873
2285
|
const prepared2 = await prepareTileflowBuildingModel(id, files2);
|
|
1874
2286
|
buildings[id] = {
|
|
1875
|
-
fileName:
|
|
2287
|
+
fileName: tileflowModelFileName(id, prepared2.sha256),
|
|
1876
2288
|
bytes: prepared2.bytes,
|
|
1877
|
-
sha256: prepared2.sha256
|
|
2289
|
+
sha256: prepared2.sha256,
|
|
2290
|
+
sources
|
|
1878
2291
|
};
|
|
1879
2292
|
} catch (error) {
|
|
1880
2293
|
issues.push({
|
|
@@ -2215,7 +2628,7 @@ async function prepareTileflowCatalogIcons(project, options) {
|
|
|
2215
2628
|
const buildingModels = {};
|
|
2216
2629
|
for (const [id, model] of Object.entries(models.buildings)) {
|
|
2217
2630
|
const fileName = `models/${mapName}/${model.fileName}`;
|
|
2218
|
-
assets.push({ contentType:
|
|
2631
|
+
assets.push({ contentType: tileflowModelContentType, fileName, source: model.bytes });
|
|
2219
2632
|
buildingModels[id] = joinUrl(options.assetBaseUrl, fileName);
|
|
2220
2633
|
}
|
|
2221
2634
|
mapAssets[mapName] = {
|
|
@@ -2833,7 +3246,6 @@ export {
|
|
|
2833
3246
|
prepareTileflowBuildingModel,
|
|
2834
3247
|
packageTileflowLandmarkTile,
|
|
2835
3248
|
encodeTileflowLandmarkRegion,
|
|
2836
|
-
createTileflowModelBundle,
|
|
2837
3249
|
prepareTileflowMapTreeModels,
|
|
2838
3250
|
renderTileflowIconAtlasPreview,
|
|
2839
3251
|
TileflowIconCompilationError,
|
package/dist/fonts.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TileflowFontBundleManifest, MapLibreStyle } from '@tileflow/core';
|
|
2
2
|
import { TileflowEffectiveFontSourceIdentity, TileflowBuildStyles, TileflowBuildCatalog } from '@tileflow/core/build';
|
|
3
|
-
import { T as TileflowBuildAsset } from './icons-
|
|
3
|
+
import { T as TileflowBuildAsset } from './icons-BIHpw5Zy.js';
|
|
4
4
|
|
|
5
5
|
type TileflowFontCompilationTarget = 'hosted' | 'local';
|
|
6
6
|
type TileflowFontCompilationIssue = {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TileflowIconAppearance, TileflowIconLayoutMetadata, TileflowIconSetPin, TileflowLandmarkRegion,
|
|
1
|
+
import { TileflowIconAppearance, TileflowIconLayoutMetadata, TileflowIconSetPin, TileflowLandmarkRegion, TileflowLandmarkFootprint, TileflowModelDirectory, TileflowTreeModel, TileflowIconSpriteIndex, TileflowIconSource, TileflowIconsLockfileV1, TileflowIconCompositionV1, TileflowIconPackageFileName, TileflowIconPackageManifest, TileflowIconDirectory } from '@tileflow/core';
|
|
2
2
|
import { TileflowEffectiveModelSourceIdentity, TileflowEffectiveIconSourceIdentity, TileflowBuildCatalog, TileflowPreparedMapAssets } from '@tileflow/core/build';
|
|
3
3
|
|
|
4
4
|
type TileflowRenderedIconCell = {
|
|
@@ -44,6 +44,12 @@ type PreparedTileflowBuildingModel = {
|
|
|
44
44
|
fileName: string;
|
|
45
45
|
bytes: Uint8Array;
|
|
46
46
|
sha256: string;
|
|
47
|
+
/** The glTF binaries it was prepared from, nearest level first. */
|
|
48
|
+
sources: readonly Readonly<{
|
|
49
|
+
level: 'near' | 'mid' | 'far';
|
|
50
|
+
sha256: string;
|
|
51
|
+
byteLength: number;
|
|
52
|
+
}>[];
|
|
47
53
|
};
|
|
48
54
|
/** A building model's glTF binaries, nearest level first. */
|
|
49
55
|
type TileflowBuildingModelLevels = Readonly<{
|
|
@@ -73,39 +79,26 @@ type TileflowLandmarkSource = Readonly<{
|
|
|
73
79
|
/**
|
|
74
80
|
* Packages the landmarks standing in one tile of a landmarks tileset into that tile's prepared
|
|
75
81
|
* model file, the landmarks in the order of their ids. Every landmark's origin must fall in the
|
|
76
|
-
* same zoom-14 tile.
|
|
82
|
+
* same zoom-14 tile. It also returns each landmark's place and footprint, as the region's index
|
|
83
|
+
* names them. The result depends only on its input.
|
|
77
84
|
*/
|
|
78
85
|
declare function packageTileflowLandmarkTile(landmarks: readonly TileflowLandmarkSource[]): Promise<{
|
|
79
86
|
x: number;
|
|
80
87
|
y: number;
|
|
81
88
|
bytes: Uint8Array;
|
|
82
89
|
sha256: string;
|
|
90
|
+
landmarks: readonly TileflowLandmarkFootprint[];
|
|
83
91
|
}>;
|
|
84
92
|
/**
|
|
85
|
-
* Writes the index of one region of a landmarks tileset, `{region: [x, y], tiles: [[x, y,
|
|
86
|
-
*
|
|
93
|
+
* Writes the index of one region of a landmarks tileset, `{region: [x, y], tiles: [[x, y, sha256,
|
|
94
|
+
* landmarks], ...]}`, as the bytes the tileset serves, with their SHA-256. A tile's `landmarks` are
|
|
95
|
+
* the ones `packageTileflowLandmarkTile` returned for it, or `readTileflowLandmarkTileFootprints`
|
|
96
|
+
* reads back from its bytes.
|
|
87
97
|
*/
|
|
88
98
|
declare function encodeTileflowLandmarkRegion(region: TileflowLandmarkRegion): {
|
|
89
99
|
bytes: Uint8Array;
|
|
90
100
|
sha256: string;
|
|
91
101
|
};
|
|
92
|
-
/** A map's building models as the model bundle a hosted deploy uploads before its styles. */
|
|
93
|
-
type TileflowModelBundle = {
|
|
94
|
-
/** The SHA-256 of the manifest's canonical JSON. */
|
|
95
|
-
contentHash: string;
|
|
96
|
-
manifest: TileflowModelBundleManifest;
|
|
97
|
-
/** Each model's prepared file, named as the manifest names it. */
|
|
98
|
-
files: {
|
|
99
|
-
contentType: string;
|
|
100
|
-
fileName: string;
|
|
101
|
-
source: Uint8Array;
|
|
102
|
-
}[];
|
|
103
|
-
};
|
|
104
|
-
/**
|
|
105
|
-
* Bundles a map's prepared building models, or returns undefined when it has none. Throws a
|
|
106
|
-
* `RangeError` an author can act on when the models together are too large to host.
|
|
107
|
-
*/
|
|
108
|
-
declare function createTileflowModelBundle(buildings: Readonly<Record<string, PreparedTileflowBuildingModel>>): Promise<TileflowModelBundle | undefined>;
|
|
109
102
|
type PreparedTileflowTreeModels = {
|
|
110
103
|
/** The prepared tree models by id. */
|
|
111
104
|
models: Record<string, TileflowTreeModel>;
|
|
@@ -421,4 +414,4 @@ declare function readTileflowIconDirectory(source: TileflowIconDirectory, option
|
|
|
421
414
|
target: TileflowIconCompilationTarget;
|
|
422
415
|
}): Promise<ReadTileflowIconDirectoryResult>;
|
|
423
416
|
|
|
424
|
-
export {
|
|
417
|
+
export { readTileflowIconsLockfileText as $, TileflowIconCompilationError as A, type TileflowIconCompilationIssue as B, type CompileTileflowIconPackagesOptions as C, type TileflowIconCompilationTarget as D, type TileflowIconDirectoryEntry as E, type TileflowIconDirectorySourceFile as F, type TileflowIconReplacement as G, type TileflowMapIconPackageBinding as H, type InspectTileflowIconCatalogsOptions as I, type TileflowRenderedIcon as J, type TileflowRenderedIconCell as K, compileTileflowIconPackages as L, composeTileflowIconSources as M, getTileflowIconCacheDirectory as N, getTileflowIconWatchPaths as O, type PreparedTileflowBuildCatalog as P, iconFileExtensions as Q, type ReadTileflowIconDirectoryResult as R, inspectTileflowIconCatalogs as S, type TileflowBuildAsset as T, loadTileflowIconSetArtifact as U, type VerifiedTileflowIconArtifact as V, packTileflowRenderedIcons as W, prepareTileflowCatalogIcons as X, prepareTileflowMapTreeModels as Y, readTileflowIconDirectory as Z, readTileflowIconsLockfile as _, type TileflowIconResolutionOptions as a, renderTileflowIconAtlasPreview as a0, storeTileflowIconSetArtifact as a1, verifyTileflowIconArtifact as a2, writeTileflowIconsLockfile as a3, type TileflowSourceCatalog as b, type PreparedTileflowCatalog as c, type TileflowBuildingModelLevels as d, type TileflowLandmarkSource as e, encodeTileflowLandmarkRegion as f, prepareTileflowBuildingModel as g, type CompileTileflowIconPackagesResult as h, type CompiledTileflowIconPackage as i, type CompiledTileflowIconPackageFile as j, type ComposeTileflowIconSourcesOptions as k, type PreparedTileflowBuildingModel as l, type TileflowComposedIconContributor as m, type TileflowComposedIconSources as n, type TileflowComposedIconWinner as o, packageTileflowLandmarkTile as p, type TileflowIconCacheOptions as q, type TileflowIconCatalog as r, type TileflowIconCatalogAtlasRectangle as s, type TileflowIconCatalogContributor as t, type TileflowIconCatalogIcon as u, type TileflowIconCatalogIconSource as v, type TileflowIconCatalogInspection as w, type TileflowIconCatalogMap as x, type TileflowIconCatalogRenderedDensity as y, type TileflowIconCatalogSourceFormat as z };
|
package/dist/icons.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import '@tileflow/core';
|
|
2
2
|
import '@tileflow/core/build';
|
|
3
|
-
export { C as CompileTileflowIconPackagesOptions, h as CompileTileflowIconPackagesResult, i as CompiledTileflowIconPackage, j as CompiledTileflowIconPackageFile, k as ComposeTileflowIconSourcesOptions, I as InspectTileflowIconCatalogsOptions, P as PreparedTileflowBuildCatalog, l as PreparedTileflowBuildingModel, c as PreparedTileflowCatalog, R as ReadTileflowIconDirectoryResult, T as TileflowBuildAsset, m as TileflowComposedIconContributor, n as TileflowComposedIconSources, o as TileflowComposedIconWinner, q as TileflowIconCacheOptions, r as TileflowIconCatalog, s as TileflowIconCatalogAtlasRectangle, t as TileflowIconCatalogContributor, u as TileflowIconCatalogIcon, v as TileflowIconCatalogIconSource, w as TileflowIconCatalogInspection, x as TileflowIconCatalogMap, y as TileflowIconCatalogRenderedDensity, z as TileflowIconCatalogSourceFormat, A as TileflowIconCompilationError, B as TileflowIconCompilationIssue, D as TileflowIconCompilationTarget, E as TileflowIconDirectoryEntry, F as TileflowIconDirectorySourceFile, G as TileflowIconReplacement, a as TileflowIconResolutionOptions, H as TileflowMapIconPackageBinding, J as
|
|
3
|
+
export { C as CompileTileflowIconPackagesOptions, h as CompileTileflowIconPackagesResult, i as CompiledTileflowIconPackage, j as CompiledTileflowIconPackageFile, k as ComposeTileflowIconSourcesOptions, I as InspectTileflowIconCatalogsOptions, P as PreparedTileflowBuildCatalog, l as PreparedTileflowBuildingModel, c as PreparedTileflowCatalog, R as ReadTileflowIconDirectoryResult, T as TileflowBuildAsset, m as TileflowComposedIconContributor, n as TileflowComposedIconSources, o as TileflowComposedIconWinner, q as TileflowIconCacheOptions, r as TileflowIconCatalog, s as TileflowIconCatalogAtlasRectangle, t as TileflowIconCatalogContributor, u as TileflowIconCatalogIcon, v as TileflowIconCatalogIconSource, w as TileflowIconCatalogInspection, x as TileflowIconCatalogMap, y as TileflowIconCatalogRenderedDensity, z as TileflowIconCatalogSourceFormat, A as TileflowIconCompilationError, B as TileflowIconCompilationIssue, D as TileflowIconCompilationTarget, E as TileflowIconDirectoryEntry, F as TileflowIconDirectorySourceFile, G as TileflowIconReplacement, a as TileflowIconResolutionOptions, H as TileflowMapIconPackageBinding, J as TileflowRenderedIcon, K as TileflowRenderedIconCell, b as TileflowSourceCatalog, V as VerifiedTileflowIconArtifact, L as compileTileflowIconPackages, M as composeTileflowIconSources, N as getTileflowIconCacheDirectory, O as getTileflowIconWatchPaths, Q as iconFileExtensions, S as inspectTileflowIconCatalogs, U as loadTileflowIconSetArtifact, W as packTileflowRenderedIcons, X as prepareTileflowCatalogIcons, Y as prepareTileflowMapTreeModels, Z as readTileflowIconDirectory, _ as readTileflowIconsLockfile, $ as readTileflowIconsLockfileText, a0 as renderTileflowIconAtlasPreview, a1 as storeTileflowIconSetArtifact, a2 as verifyTileflowIconArtifact, a3 as writeTileflowIconsLockfile } from './icons-BIHpw5Zy.js';
|
package/dist/icons.js
CHANGED
|
@@ -2,7 +2,6 @@ import {
|
|
|
2
2
|
TileflowIconCompilationError,
|
|
3
3
|
compileTileflowIconPackages,
|
|
4
4
|
composeTileflowIconSources,
|
|
5
|
-
createTileflowModelBundle,
|
|
6
5
|
getTileflowIconCacheDirectory,
|
|
7
6
|
getTileflowIconWatchPaths,
|
|
8
7
|
iconFileExtensions,
|
|
@@ -18,13 +17,12 @@ import {
|
|
|
18
17
|
storeTileflowIconSetArtifact,
|
|
19
18
|
verifyTileflowIconArtifact,
|
|
20
19
|
writeTileflowIconsLockfile
|
|
21
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-5RT4CHSK.js";
|
|
22
21
|
import "./chunk-7C4F6GAX.js";
|
|
23
22
|
export {
|
|
24
23
|
TileflowIconCompilationError,
|
|
25
24
|
compileTileflowIconPackages,
|
|
26
25
|
composeTileflowIconSources,
|
|
27
|
-
createTileflowModelBundle,
|
|
28
26
|
getTileflowIconCacheDirectory,
|
|
29
27
|
getTileflowIconWatchPaths,
|
|
30
28
|
iconFileExtensions,
|
package/dist/preview.js
CHANGED
|
@@ -348,6 +348,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
348
348
|
import {loadTileflowStyleFonts} from "${basePath}/__runtime/tileflow-browser.js";
|
|
349
349
|
import {registerTileflowContourProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
350
350
|
import {registerTileflowPmtilesProtocol} from "${basePath}/__runtime/tileflow-browser.js";
|
|
351
|
+
import {attachTileflowNotices} from "${basePath}/__runtime/tileflow-browser.js";
|
|
351
352
|
import {createTileflowBuildings3D, createTileflowStreetLights, createTileflowVegetation} from "${basePath}/__runtime/tileflow-browser.js";
|
|
352
353
|
import {readTileflowSunPreset, resolveTileflowSun, tileflowDefaultSunPreset} from "${basePath}/__runtime/tileflow-browser.js";
|
|
353
354
|
|
|
@@ -2383,6 +2384,7 @@ function renderTileflowPreviewHtml(preview, basePath, initialStatus, isSemanticP
|
|
|
2383
2384
|
...resolveInitialMapOptions(previewMapOptions)
|
|
2384
2385
|
});
|
|
2385
2386
|
globalThis.__tileflowPreviewMap = map;
|
|
2387
|
+
attachTileflowNotices(map);
|
|
2386
2388
|
devtoolsObserver?.attach(map);
|
|
2387
2389
|
installMapBenchmark(map);
|
|
2388
2390
|
if (window.parent === window) map.addControl(new maplibregl.NavigationControl(), "top-right");
|
package/dist/server.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import '@tileflow/core';
|
|
|
5
5
|
import '@tileflow/core/build';
|
|
6
6
|
import '@tileflow/core/manifest';
|
|
7
7
|
import '@tileflow/core/native-profile';
|
|
8
|
-
import './icons-
|
|
8
|
+
import './icons-BIHpw5Zy.js';
|
|
9
9
|
import './config.js';
|
|
10
10
|
|
|
11
11
|
declare const tileflowComparisonSchemaVersion: 1;
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
parseTileflowLineBackgroundGlyphPath,
|
|
6
6
|
tileflowArtifactSessionSchemaVersion,
|
|
7
7
|
tileflowLineBackgroundGlyphRoute
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-2F7LOYQI.js";
|
|
9
9
|
import {
|
|
10
10
|
getTileflowAssetBasePath,
|
|
11
11
|
getTileflowAssetFileName,
|
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
normalizeTileflowBasePath,
|
|
14
14
|
resolveTileflowArtifactPublicUrls
|
|
15
15
|
} from "./chunk-UTRO4IBQ.js";
|
|
16
|
-
import "./chunk-
|
|
16
|
+
import "./chunk-5RT4CHSK.js";
|
|
17
17
|
import "./chunk-7C4F6GAX.js";
|
|
18
18
|
import {
|
|
19
19
|
TileflowStyleValidationError
|
|
@@ -45,7 +45,7 @@ Baedeker, Cívica, 1777, Siegfried, Soundings, and Blueprint declare only
|
|
|
45
45
|
`[soundingsIcons]`, and `[blueprintIcons]`, respectively. No official root composes another map's assets even though all
|
|
46
46
|
use the semantic compiler ABI. Baedeker's directory contains eight original travel-atlas patterns;
|
|
47
47
|
its browser-derived Mapterhorn contours use runtime terrain tiles and are not part of the icon
|
|
48
|
-
package. Soundings owns
|
|
48
|
+
package. Soundings owns eleven original nautical symbols and
|
|
49
49
|
patterns. Blueprint owns four technical blueprint patterns and one schematic POI node; its
|
|
50
50
|
unbundled Mapterhorn contours remain runtime terrain, and its text uses the canonical Noto Sans
|
|
51
51
|
glyph provider. An application map may inherit a root's exact array, replace it, clear it with `[]`,
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# Prepare recolorable building models
|
|
2
|
+
|
|
3
|
+
Custom buildings and landmarks use the same preparation path. Export uncompressed glTF 2.0
|
|
4
|
+
binaries in metres, Y up, with the origin on the ground. The default scene's triangles and node
|
|
5
|
+
transforms are read. Building preparation retains physical materials and embedded PNG/JPEG
|
|
6
|
+
textures. Animations, skins and morph targets are not evaluated; export their static geometry.
|
|
7
|
+
|
|
8
|
+
## Separate colour from architectural role
|
|
9
|
+
|
|
10
|
+
Material names select a palette slot: `surface`, `secondary`, `glass`, `accent` or `roof`.
|
|
11
|
+
The optional `material.extras.tileflow.lightingRole` selects lighting independently:
|
|
12
|
+
|
|
13
|
+
```json
|
|
14
|
+
{
|
|
15
|
+
"name": "accent",
|
|
16
|
+
"extras": {
|
|
17
|
+
"tileflow": {
|
|
18
|
+
"lightingRole": "door"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"pbrMetallicRoughness": {
|
|
22
|
+
"baseColorFactor": [0.8, 0.8, 0.8, 1]
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This door can take the map's `accent` colour while receiving its `door` lighting profile. Accepted
|
|
28
|
+
roles are `unclassified`, `wall`, `door`, `roof`, `window`, `lamp` and `logo`. A role describes
|
|
29
|
+
geometry; it does not turn on light by itself. The map supplies emission through
|
|
30
|
+
`buildings.threeDimensional.modelLighting`.
|
|
31
|
+
|
|
32
|
+
Legacy models without an explicit role use these defaults:
|
|
33
|
+
|
|
34
|
+
| Palette slot | Lighting role |
|
|
35
|
+
| -------------------------------- | ------------- |
|
|
36
|
+
| `surface`, `secondary`, `accent` | `wall` |
|
|
37
|
+
| `glass` | `window` |
|
|
38
|
+
| `roof` | `roof` |
|
|
39
|
+
|
|
40
|
+
Doors, lamps and logos require an explicit role. Use `unclassified` for a part that must not inherit
|
|
41
|
+
a role. Invalid role names fail preparation. An exporter's numeric material suffix, such as
|
|
42
|
+
`surface.001`, is ignored.
|
|
43
|
+
|
|
44
|
+
Keep a stone relief in a wall role when it should receive the wall's lighting. A separate colour
|
|
45
|
+
slot can distinguish its stone without creating a separate light source. Mark roof geometry as
|
|
46
|
+
roof even when it shares a colour with a facade.
|
|
47
|
+
|
|
48
|
+
## Physical materials
|
|
49
|
+
|
|
50
|
+
The importer retains these glTF 2.0 channels independently of palette and role:
|
|
51
|
+
|
|
52
|
+
| Channel | Inputs |
|
|
53
|
+
| ------------------ | --------------------------------------------------------------------------------- |
|
|
54
|
+
| Base colour | Linear `baseColorFactor`, linear `COLOR_0` RGB/RGBA, sRGB base-colour texture |
|
|
55
|
+
| Reflection | `metallicFactor`, `roughnessFactor`, texture blue/green channels |
|
|
56
|
+
| Surface normal | Tangent-space normal texture and `scale`; tangent basis from geometry and UVs |
|
|
57
|
+
| Ambient visibility | Occlusion texture red channel and `strength`, multiplied by geometric visibility |
|
|
58
|
+
| Emission | Linear `emissiveFactor`, sRGB emissive texture, `KHR_materials_emissive_strength` |
|
|
59
|
+
| Coverage | `OPAQUE`, `MASK` with `alphaCutoff`, or `BLEND`; `doubleSided` |
|
|
60
|
+
| Unlit | `KHR_materials_unlit` |
|
|
61
|
+
|
|
62
|
+
`TEXCOORD_0`, `TEXCOORD_1`, glTF sampler wrapping/filtering and `KHR_texture_transform` are retained.
|
|
63
|
+
Every referenced channel must have its declared UV set. UV and colour seams survive welding.
|
|
64
|
+
Colour textures and lit output use the scene's gamma-2.2 transfer approximation.
|
|
65
|
+
Role emission mixes the complete material radiance toward its target, including authored emission.
|
|
66
|
+
Images must be embedded in buffer views or PNG/JPEG data URIs; preparation never fetches external
|
|
67
|
+
images. Other image formats and physical-material extensions are not supported.
|
|
68
|
+
|
|
69
|
+
Each level accepts at most 256 distinct materials, 32 textures, 4096 pixels per image dimension
|
|
70
|
+
and 16,777,216 decoded image pixels in total. The complete prepared file remains limited to 16 MiB.
|
|
71
|
+
Invalid references, missing UVs and image headers that disagree with their dimensions fail preparation.
|
|
72
|
+
|
|
73
|
+
Without overrides, a physical model retains its authored material. `modelLighting` can tint and
|
|
74
|
+
override roughness/metallicity by role. Explicit `modelColors` take precedence over role tints;
|
|
75
|
+
textures and vertex colours still modulate the chosen colour. Keep vertex colours neutral when
|
|
76
|
+
they should not affect recolouring.
|
|
77
|
+
|
|
78
|
+
Colour and normal textures, metallic reflection and alpha coverage use the browser's physical
|
|
79
|
+
model path. Ambient visibility never attenuates solar light or emission. Masked pixels also leave
|
|
80
|
+
holes in dynamic solar shadows; blended surfaces cast an alpha-tested shadow at 0.5. Transparent
|
|
81
|
+
material ranges draw back to front after opaque surfaces; intersecting transparent triangles are
|
|
82
|
+
not sorted individually.
|
|
83
|
+
|
|
84
|
+
## Explicit area emitters
|
|
85
|
+
|
|
86
|
+
A `door` role lights the surface. To project its spill, declare a rectangle in
|
|
87
|
+
`node.extras.tileflow.emitters`. This glTF fragment uses node-local metres and Y up:
|
|
88
|
+
|
|
89
|
+
```json
|
|
90
|
+
{
|
|
91
|
+
"extras": {
|
|
92
|
+
"tileflow": {
|
|
93
|
+
"emitters": [
|
|
94
|
+
{
|
|
95
|
+
"start": [-1, 0, 0],
|
|
96
|
+
"end": [1, 0, 0],
|
|
97
|
+
"height": 3,
|
|
98
|
+
"depth": 0.2,
|
|
99
|
+
"role": "door",
|
|
100
|
+
"part": "accent"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`start` and `end` form the horizontal lower edge. The spill faces the right of that edge when
|
|
109
|
+
viewed from above. `height` sets the rectangle's height; optional `depth` narrows the spill around
|
|
110
|
+
recessed jambs. Node transforms apply to the emitter. Its role supplies emission and its part
|
|
111
|
+
supplies the fallback palette colour. No emitter is inferred from material colour or geometry.
|
|
112
|
+
|
|
113
|
+
Each level accepts 128 emitters. Each projects a four-triangle, depth-tested spill with reach
|
|
114
|
+
`clamp(1.3 * height, 4, 10)` metres. This authored effect does not cast a new light or shadow onto
|
|
115
|
+
other meshes. It follows the model's visibility and theme; zero emission removes it.
|
|
116
|
+
|
|
117
|
+
## Ambient visibility
|
|
118
|
+
|
|
119
|
+
Preparation estimates each vertex's local ambient visibility using 48 deterministic,
|
|
120
|
+
cosine-weighted hemisphere samples, a two-metre reach and a two-millimetre origin bias. A spatial
|
|
121
|
+
hierarchy limits intersection work; repeated positions and normals share samples. Nearby
|
|
122
|
+
occluders contribute more than distant ones. Each level is prepared independently, including
|
|
123
|
+
occlusion between its distinct primitives. Footprint meshes do not occlude.
|
|
124
|
+
|
|
125
|
+
The result travels independently of colour. Do not bake ambient occlusion into `COLOR_0`: colour
|
|
126
|
+
also affects direct sunlight and emission. The runtime applies prepared visibility only to the
|
|
127
|
+
ambient contribution; solar visibility still comes from the dynamic shadow map.
|
|
128
|
+
|
|
129
|
+
`threeDimensional.modelOcclusion` controls strength from zero to one and defaults to `0.75`.
|
|
130
|
+
`threeDimensional.occlusion: false` also disables model ambient occlusion. Changing these
|
|
131
|
+
runtime controls does not prepare the geometry again.
|
|
132
|
+
|
|
133
|
+
Preparation cost grows with unique vertices, samples and nearby geometry. It is paid when a raw
|
|
134
|
+
model is prepared, never per frame. Runtime occlusion adds no draw pass or texture and uses the
|
|
135
|
+
existing visibility byte. It describes opaque geometry at rest; it does not follow moving parts,
|
|
136
|
+
other models, the ground or surrounding buildings.
|
|
137
|
+
|
|
138
|
+
## Files and compatibility
|
|
139
|
+
|
|
140
|
+
Use `<id>.glb`, optionally `<id>.mid.glb` and `<id>.far.glb`. A mesh whose material is `footprint`
|
|
141
|
+
defines the area replaced by the model and is not drawn. Otherwise preparation derives a footprint.
|
|
142
|
+
See the [Core building guide](https://github.com/tileflow/tileflow-sdk/blob/main/packages/core/docs/buildings-3d.md)
|
|
143
|
+
for placement and limits.
|
|
144
|
+
|
|
145
|
+
Prepared files retain a 16-byte vertex:
|
|
146
|
+
|
|
147
|
+
| Bytes | Content |
|
|
148
|
+
| ----- | -------------------------------- |
|
|
149
|
+
| 0–5 | Quantized position |
|
|
150
|
+
| 6–7 | Padding |
|
|
151
|
+
| 8–10 | Normal |
|
|
152
|
+
| 11 | Padding |
|
|
153
|
+
| 12 | Palette slot |
|
|
154
|
+
| 13 | Shade within that slot |
|
|
155
|
+
| 14 | Ambient visibility, 0–255 |
|
|
156
|
+
| 15 | Architectural lighting role, 0–6 |
|
|
157
|
+
|
|
158
|
+
The model root's `extras.tileflow.lightingRoles: 1` declares the last byte. Older files without
|
|
159
|
+
that marker use the legacy palette mapping. Existing fully exposed visibility values stay fully
|
|
160
|
+
exposed; reprepare the original GLBs to obtain geometric occlusion. Geometry, normals and palette
|
|
161
|
+
slots are not modified by the visibility calculation.
|
|
162
|
+
|
|
163
|
+
Roles and visibility survive meshoptimizer compression. The existing `_PAINT` accessor and vertex
|
|
164
|
+
stride are retained; the role occupies previously unused padding. Prepared files are an SDK
|
|
165
|
+
transport format, while material extras are the authoring interface.
|
|
166
|
+
|
|
167
|
+
Physical data is optional `extras.tileflow.surface` metadata on each level, with `version: 1`.
|
|
168
|
+
Two VEC4 accessors address a separate 32-byte stream: UV0/UV1 followed by linear vertex RGBA.
|
|
169
|
+
The metadata also holds material draw ranges, channel references, embedded images and emitters.
|
|
170
|
+
Ranges preserve each source mesh's vertical emission bounds independently of its material.
|
|
171
|
+
The original primitive layout remains unchanged. Older readers draw its legacy geometry and
|
|
172
|
+
palette; they ignore the optional physical data. New readers preserve the legacy shading path
|
|
173
|
+
when physical data is absent. Reprepare source GLBs to obtain the new channels.
|
|
174
|
+
|
|
175
|
+
The auxiliary stream costs 32 bytes per retained vertex on the GPU. Each resident texture uses
|
|
176
|
+
four bytes per pixel, plus about one third for mipmaps when enabled. Decoded images stay available
|
|
177
|
+
for context restoration until the model is evicted. Physical materials add one draw per material
|
|
178
|
+
range per colour/shadow pass; explicit emitters add one four-triangle draw each. No environment
|
|
179
|
+
cube map or screen-space effect is required.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tileflow/dev",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
3
|
+
"version": "0.1.0-alpha.34",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"description": "Node development utilities for Tileflow configs and builds.",
|
|
6
6
|
"type": "module",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"dependencies": {
|
|
85
85
|
"@mapbox/vector-tile": "3.0.0",
|
|
86
86
|
"@maplibre/maplibre-gl-style-spec": "26.4.4",
|
|
87
|
-
"@tileflow/core": ">=0.1.0-alpha.
|
|
87
|
+
"@tileflow/core": ">=0.1.0-alpha.37 <0.1.0-beta.0",
|
|
88
88
|
"chokidar": "4.0.3",
|
|
89
89
|
"fflate": "0.8.3",
|
|
90
90
|
"fontkit": "2.0.4",
|
|
@@ -97,7 +97,7 @@
|
|
|
97
97
|
"three": "0.185.1"
|
|
98
98
|
},
|
|
99
99
|
"devDependencies": {
|
|
100
|
-
"@tileflow/maps": "0.1.0-alpha.
|
|
100
|
+
"@tileflow/maps": "0.1.0-alpha.11"
|
|
101
101
|
},
|
|
102
102
|
"optionalDependencies": {
|
|
103
103
|
"sharp": "0.35.5"
|