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

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/dist/index.js CHANGED
@@ -897,6 +897,7 @@ var init_visual_semantics = __esm({
897
897
  "lightness",
898
898
  "fadeDuration",
899
899
  "gapWidth",
900
+ "groundOcclusion",
900
901
  "haloBlur",
901
902
  "haloWidth",
902
903
  "height",
@@ -3802,19 +3803,19 @@ function deriveGlyph(glyph, metrics) {
3802
3803
  const bitmap = new Uint8Array(width * rows);
3803
3804
  for (let row = 0; row < rows; row++) {
3804
3805
  const y = y0 + row + 0.5;
3805
- for (let column = 0; column < width; column++) {
3806
- const x = x0 + column + 0.5;
3806
+ for (let column2 = 0; column2 < width; column2++) {
3807
+ const x = x0 + column2 + 0.5;
3807
3808
  const distance = Math.min(x - cell.x0, cell.x1 - x, y - cell.y0, cell.y1 - y);
3808
3809
  const background = Math.min(CELL_VALUE, Math.max(0, CELL_EDGE + CELL_SLOPE * distance));
3809
3810
  let letter = 0;
3810
3811
  if (hasBitmap) {
3811
- const sourceColumn = x0 + column - sourceX;
3812
+ const sourceColumn = x0 + column2 - sourceX;
3812
3813
  const sourceRow = y0 + row - sourceY;
3813
3814
  if (sourceColumn >= 0 && sourceColumn < sourceWidth && sourceRow >= 0 && sourceRow < sourceHeight) {
3814
3815
  letter = glyph.bitmap[sourceRow * sourceWidth + sourceColumn];
3815
3816
  }
3816
3817
  }
3817
- bitmap[row * width + column] = Math.round(Math.max(letter, background));
3818
+ bitmap[row * width + column2] = Math.round(Math.max(letter, background));
3818
3819
  }
3819
3820
  }
3820
3821
  return {
@@ -9013,6 +9014,7 @@ function resolvePoi(request) {
9013
9014
  density: request?.density ?? 2,
9014
9015
  detailZoom: request?.detailZoom ?? 17,
9015
9016
  densityByCategory: { ...request?.densityByCategory },
9017
+ maxRankByType: { ...request?.maxRankByType },
9016
9018
  icons: request?.icons ?? true,
9017
9019
  labels: request?.labels ?? true,
9018
9020
  minZoom: request?.minZoom ?? 0,
@@ -9500,6 +9502,9 @@ function compileCategory(category, request, semantics, context, language, select
9500
9502
  }
9501
9503
  function createPoiSelection(semantics, context, language) {
9502
9504
  const { fields: fields2 } = context.data.schema;
9505
+ const typeRankCaps = Object.entries(semantics.maxRankByType).sort(
9506
+ ([a], [b]) => a.localeCompare(b)
9507
+ );
9503
9508
  const shown = /* @__PURE__ */ new Map();
9504
9509
  const iconOnlyTypes = [...new Set(semantics.placement.iconOnlyTypes)];
9505
9510
  const iconOnly = () => iconOnlyTypes.length ? ["match", ["get", fields2.poiType], iconOnlyTypes, true, false] : ["literal", false];
@@ -9535,6 +9540,14 @@ function createPoiSelection(semantics, context, language) {
9535
9540
  [">=", sizeRank, 0],
9536
9541
  ["<=", sizeRank, 16]
9537
9542
  ];
9543
+ if (typeRankCaps.length) {
9544
+ result.push([
9545
+ "match",
9546
+ ["get", fields2.poiType],
9547
+ ...typeRankCaps.flatMap(([type, rank]) => [type, ["<=", filterRank, rank]]),
9548
+ true
9549
+ ]);
9550
+ }
9538
9551
  if (!semantics.icons || !semantics.labels) return result;
9539
9552
  const textless = categories.filter((category) => !shown.get(category)?.text);
9540
9553
  const iconless = categories.filter((category) => !shown.get(category)?.icon);
@@ -13573,6 +13586,12 @@ var tileflowModelParts = Object.freeze({
13573
13586
  accent: 3,
13574
13587
  roof: 4
13575
13588
  });
13589
+ var tileflowModelArchitecturalPartNames = Object.freeze(
13590
+ Object.keys(tileflowModelParts)
13591
+ );
13592
+ function isTileflowModelPartName(value) {
13593
+ return typeof value === "string" && value.length <= 32 && /^[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*$/u.test(value);
13594
+ }
13576
13595
  var tileflowModelLightingRoles = Object.freeze({
13577
13596
  unclassified: 0,
13578
13597
  wall: 1,
@@ -13582,7 +13601,16 @@ var tileflowModelLightingRoles = Object.freeze({
13582
13601
  lamp: 5,
13583
13602
  logo: 6
13584
13603
  });
13585
- var tileflowModelDefaultLightingRoles = Object.freeze([1, 1, 4, 1, 3]);
13604
+ var architecturalRoles = {
13605
+ surface: 1,
13606
+ secondary: 1,
13607
+ glass: 4,
13608
+ accent: 1,
13609
+ roof: 3
13610
+ };
13611
+ function tileflowModelDefaultLightingRole(part) {
13612
+ return Object.hasOwn(architecturalRoles, part) ? architecturalRoles[part] : tileflowModelLightingRoles.unclassified;
13613
+ }
13586
13614
  var tileflowModelLimits = Object.freeze({
13587
13615
  /** Levels of detail, nearest first. */
13588
13616
  levels: 3,
@@ -13599,7 +13627,9 @@ var tileflowModelLimits = Object.freeze({
13599
13627
  /** Models in one prepared file, as a landmark tile holds them. */
13600
13628
  models: 512,
13601
13629
  /** Bytes of one prepared file. */
13602
- bytes: 16 * 1024 * 1024
13630
+ bytes: 16 * 1024 * 1024,
13631
+ /** Differently named parts of one level. */
13632
+ parts: 16
13603
13633
  });
13604
13634
  var tileflowModelVertexBytes = 16;
13605
13635
  function tileflowInsideRings(rings, px, py) {
@@ -13719,6 +13749,9 @@ function readModelRoleLighting(value) {
13719
13749
  ...entry.colorMix === void 0 ? {} : { colorMix: bounded(entry.colorMix, 0, 1, 1) },
13720
13750
  ...entry.metallic === void 0 ? {} : { metallic: bounded(entry.metallic, 0, 1, 0) },
13721
13751
  ...entry.roughness === void 0 ? {} : { roughness: bounded(entry.roughness, 0, 1, 1) },
13752
+ ...entry.shading === "map" || entry.shading === "physical" ? { shading: entry.shading } : {},
13753
+ ...entry.ambientOcclusion === void 0 ? {} : { ambientOcclusion: bounded(entry.ambientOcclusion, 0, 1, 1) },
13754
+ ...entry.partColorMix === void 0 ? {} : { partColorMix: bounded(entry.partColorMix, 0, 1, 1) },
13722
13755
  ...readRoleColorVariation(entry.colorVariation),
13723
13756
  emissiveHeight: Array.isArray(gradient) && gradient.length === 5 && gradient.every(
13724
13757
  (v, i) => typeof v === "number" && Number.isFinite(v) && v <= 1 && v >= (i === 4 ? -1 : 0)
@@ -13802,6 +13835,7 @@ var tileflowDefaultBuildings3DDesign = Object.freeze({
13802
13835
  windows: false,
13803
13836
  shadows: true,
13804
13837
  occlusion: true,
13838
+ groundOcclusion: 0.16,
13805
13839
  modelOcclusion: 0.75
13806
13840
  });
13807
13841
  function tileflowBuildingsMetadata(design) {
@@ -13828,7 +13862,7 @@ function readTileflowBuildings3D(metadata) {
13828
13862
  },
13829
13863
  color: color3,
13830
13864
  edges: within(value.edges, 1, 0),
13831
- edgeRadius: within(value.edgeRadius, 1, defaults4.edgeRadius),
13865
+ edgeRadius: within(value.edgeRadius, 1.5, defaults4.edgeRadius),
13832
13866
  lighting: value.lighting === "style" ? "style" : defaults4.lighting,
13833
13867
  colorVariation: within(value.colorVariation, 1, 0),
13834
13868
  verticalGradient: value.verticalGradient !== false,
@@ -13841,6 +13875,7 @@ function readTileflowBuildings3D(metadata) {
13841
13875
  ...readTileflowBuildingsEntrance(value.entrance),
13842
13876
  shadows: typeof value.shadows === "boolean" ? value.shadows : defaults4.shadows,
13843
13877
  occlusion: typeof value.occlusion === "boolean" ? value.occlusion : defaults4.occlusion,
13878
+ groundOcclusion: within(value.groundOcclusion, 1, defaults4.groundOcclusion),
13844
13879
  modelOcclusion: within(value.modelOcclusion, 1, defaults4.modelOcclusion),
13845
13880
  ...modelsOf(value.models),
13846
13881
  ...modelColorsOf(value.modelColors),
@@ -13887,8 +13922,8 @@ function modelsOf(value) {
13887
13922
  function modelColorsOf(value) {
13888
13923
  if (!isRecord7(value)) return {};
13889
13924
  const colors = {};
13890
- for (const part of Object.keys(tileflowModelParts))
13891
- if (typeof value[part] === "string") colors[part] = value[part];
13925
+ for (const [part, color3] of Object.entries(value))
13926
+ if (isTileflowModelPartName(part) && typeof color3 === "string") colors[part] = color3;
13892
13927
  return Object.keys(colors).length > 0 ? { modelColors: colors } : {};
13893
13928
  }
13894
13929
  function landmarksOf(value) {
@@ -14317,7 +14352,7 @@ function limitError(message) {
14317
14352
  return new Error(site ? `${message} Called at ${site}.` : message);
14318
14353
  }
14319
14354
  function authoringCallSite(stack) {
14320
- const locations = (stack ?? "").split("\n").slice(1).map((line) => /\(?([^()\s]+?):(\d+):(\d+)\)?$/.exec(line.trim())).filter((match3) => match3 !== null).map(([, file, line, column]) => ({ file: file.replace(/^file:\/\//, ""), line, column }));
14355
+ const locations = (stack ?? "").split("\n").slice(1).map((line) => /\(?([^()\s]+?):(\d+):(\d+)\)?$/.exec(line.trim())).filter((match3) => match3 !== null).map(([, file, line, column2]) => ({ file: file.replace(/^file:\/\//, ""), line, column: column2 }));
14321
14356
  const own = locations[0]?.file;
14322
14357
  const site = locations.find(({ file }) => file !== own && !file.startsWith("node:"));
14323
14358
  return site ? `${site.file}:${site.line}:${site.column}` : void 0;
@@ -17307,6 +17342,9 @@ var modelRoleLightingSchema = z7.object({
17307
17342
  colorMix: boundedThemeNumberSchema(0, 1).optional(),
17308
17343
  metallic: boundedThemeNumberSchema(0, 1).optional(),
17309
17344
  roughness: boundedThemeNumberSchema(0, 1).optional(),
17345
+ shading: z7.enum(["physical", "map"]).optional(),
17346
+ ambientOcclusion: boundedThemeNumberSchema(0, 1).optional(),
17347
+ partColorMix: boundedThemeNumberSchema(0, 1).optional(),
17310
17348
  colorVariation: z7.object({
17311
17349
  hue: z7.tuple([boundedThemeNumberSchema(0, 360), boundedThemeNumberSchema(0, 360)]),
17312
17350
  saturation: z7.tuple([boundedThemeNumberSchema(0, 100), boundedThemeNumberSchema(0, 100)]),
@@ -17333,7 +17371,7 @@ var buildingsModuleSchema = z7.object({
17333
17371
  lighting: z7.enum(["style", "sun"]).optional(),
17334
17372
  colorVariation: boundedThemeNumberSchema(0, 1).optional(),
17335
17373
  edges: boundedThemeNumberSchema(0, 1).optional(),
17336
- edgeRadius: z7.number().min(0).max(1).optional(),
17374
+ edgeRadius: z7.number().min(0).max(1.5).optional(),
17337
17375
  edgeColor: themeColorValueSchema.optional(),
17338
17376
  nightLight: boundedThemeNumberSchema(0, 2).optional(),
17339
17377
  floodLight: z7.object({
@@ -17356,6 +17394,7 @@ var buildingsModuleSchema = z7.object({
17356
17394
  }).strict().optional(),
17357
17395
  entrance: z7.object({ buildings: entranceBandSchema.optional(), models: entranceBandSchema.optional() }).strict().optional(),
17358
17396
  occlusion: z7.boolean().optional(),
17397
+ groundOcclusion: boundedThemeNumberSchema(0, 1).optional(),
17359
17398
  modelOcclusion: boundedThemeNumberSchema(0, 1).optional(),
17360
17399
  shadows: z7.boolean().optional(),
17361
17400
  windows: z7.boolean().optional(),
@@ -17392,13 +17431,14 @@ var buildingsModuleSchema = z7.object({
17392
17431
  }).optional()
17393
17432
  }).strict()
17394
17433
  ).max(256).optional(),
17395
- modelColors: z7.object({
17396
- surface: themeColorValueSchema.optional(),
17397
- secondary: themeColorValueSchema.optional(),
17398
- glass: themeColorValueSchema.optional(),
17399
- accent: themeColorValueSchema.optional(),
17400
- roof: themeColorValueSchema.optional()
17401
- }).strict().optional(),
17434
+ modelColors: z7.record(
17435
+ z7.string().refine(isTileflowModelPartName, {
17436
+ message: "A model part is named in lower-case words joined by hyphens or underscores, at most 32 characters."
17437
+ }),
17438
+ themeColorValueSchema
17439
+ ).refine((colors) => Object.keys(colors).length <= 64, {
17440
+ message: "A map colours at most 64 model parts."
17441
+ }).optional(),
17402
17442
  landmarks: z7.union([
17403
17443
  z7.boolean(),
17404
17444
  z7.object({
@@ -17603,6 +17643,17 @@ var poiModuleSchema = z7.object({
17603
17643
  type: z7.literal("poi"),
17604
17644
  detailZoom: z7.number().finite().gt(16).max(24).optional(),
17605
17645
  categories: z7.array(z7.enum(tileflowPoiCategories)).optional(),
17646
+ maxRankByType: z7.record(
17647
+ z7.string().regex(/^[a-z0-9]+(?:_[a-z0-9]+)*$/u, "Expected a World POI type"),
17648
+ z7.union([
17649
+ z7.literal(0),
17650
+ z7.literal(1),
17651
+ z7.literal(2),
17652
+ z7.literal(3),
17653
+ z7.literal(4),
17654
+ z7.literal(5)
17655
+ ])
17656
+ ).describe("Fixed maximum World filter rank per POI type, independent of zoom detail.").optional(),
17606
17657
  color: z7.enum(["uniform", "category"]).optional(),
17607
17658
  density: z7.union([z7.literal(1), z7.literal(2), z7.literal(3), z7.literal(4), z7.literal(5)]).describe(
17608
17659
  "POI density from 1 to 5. Defaults to 2; native collisions determine which eligible names fit."
@@ -20599,6 +20650,7 @@ function tileflowBuildings3DDesignOf(module, context, layers2, buildingModels, t
20599
20650
  ...readTileflowBuildingsEntrance(style.entrance),
20600
20651
  shadows: typeof style.shadows === "boolean" ? style.shadows : defaults4.shadows,
20601
20652
  occlusion: typeof style.occlusion === "boolean" ? style.occlusion : defaults4.occlusion,
20653
+ groundOcclusion: number(style.groundOcclusion, defaults4.groundOcclusion),
20602
20654
  modelOcclusion: number(style.modelOcclusion, defaults4.modelOcclusion),
20603
20655
  ...placedModels(style.models, buildingModels),
20604
20656
  ...modelColours(style.modelColors),
@@ -20622,7 +20674,7 @@ function placedModels(value, served) {
20622
20674
  throw new Error(
20623
20675
  `The buildings module places missing 3D models: ${missing.join(", ")}. Add ${missing.map((id2) => `${id2}.glb`).join(
20624
20676
  ", "
20625
- )} to a map.models directory, with materials named surface, secondary, glass, accent or roof, and run the Node build so the directories are prepared.`
20677
+ )} to a map.models directory, with each material named after its part, such as surface, glass, roof or water, and run the Node build so the directories are prepared.`
20626
20678
  );
20627
20679
  return {
20628
20680
  models: placements.map((entry) => ({
@@ -22949,17 +23001,17 @@ function parseBoundaryLookupCsv(text) {
22949
23001
  );
22950
23002
  }
22951
23003
  const row = {};
22952
- header.forEach((column, position) => {
23004
+ header.forEach((column2, position) => {
22953
23005
  const cell = record4[position];
22954
- if (cell === "" || !lookupColumns.has(column)) return;
22955
- if (numericLookupColumns.has(column)) {
23006
+ if (cell === "" || !lookupColumns.has(column2)) return;
23007
+ if (numericLookupColumns.has(column2)) {
22956
23008
  const number = Number(cell);
22957
23009
  if (!Number.isSafeInteger(number)) {
22958
- throw new Error(`Boundaries lookup CSV row ${index + 2} has a non-integer ${column}.`);
23010
+ throw new Error(`Boundaries lookup CSV row ${index + 2} has a non-integer ${column2}.`);
22959
23011
  }
22960
- row[column] = number;
23012
+ row[column2] = number;
22961
23013
  } else {
22962
- row[column] = cell;
23014
+ row[column2] = cell;
22963
23015
  }
22964
23016
  });
22965
23017
  if (row.class_tier !== void 0 && !isClassTier(row.class_tier)) {
@@ -25196,6 +25248,7 @@ function checkTileflowModelSurface(surface, vertices, indices) {
25196
25248
  const value = attributes[i];
25197
25249
  if (!Number.isFinite(value) || Math.abs(value) > 65536) fail("unbounded vertex attributes");
25198
25250
  if (i % 8 >= 4 && (value < 0 || value > 1)) fail("vertex RGBA must be zero to one");
25251
+ if ((i % 8 === 2 || i % 8 === 3) && (value < 0 || value > 1)) fail("UV1 must be zero to one");
25199
25252
  }
25200
25253
  if (!Array.isArray(materials) || materials.length < 1 || materials.length > tileflowModelSurfaceLimits.materials)
25201
25254
  fail("material count exceeds the limit");
@@ -25277,7 +25330,7 @@ function checkTileflowModelSurface(surface, vertices, indices) {
25277
25330
  fail("emitter count exceeds the limit");
25278
25331
  for (const emitter of emitters) {
25279
25332
  if (!emitter || typeof emitter !== "object") fail("invalid area emitter");
25280
- if (!tuple(emitter.start, 3, -2e3, 2e3) || !tuple(emitter.end, 3, -2e3, 2e3) || !number(emitter.height, 0.01, 1e3) || emitter.depth !== void 0 && !number(emitter.depth, 0, 100) || !Object.hasOwn(tileflowModelLightingRoles, emitter.role) || !Object.hasOwn(tileflowModelParts, emitter.part))
25333
+ if (!tuple(emitter.start, 3, -2e3, 2e3) || !tuple(emitter.end, 3, -2e3, 2e3) || !number(emitter.height, 0.01, 1e3) || emitter.depth !== void 0 && !number(emitter.depth, 0, 100) || !Object.hasOwn(tileflowModelLightingRoles, emitter.role) || !isTileflowModelPartName(emitter.part))
25281
25334
  fail("invalid area emitter");
25282
25335
  if (Math.hypot(emitter.end[0] - emitter.start[0], emitter.end[1] - emitter.start[1]) < 1e-3 || Math.abs(emitter.end[2] - emitter.start[2]) > 0.01)
25283
25336
  fail("an area emitter needs a horizontal lower edge");
@@ -25320,7 +25373,6 @@ var levelNames = ["near", "mid", "far"];
25320
25373
  var meshopt = "EXT_meshopt_compression";
25321
25374
  var quantisation = "KHR_mesh_quantization";
25322
25375
  var steps = 32767;
25323
- var partCount2 = Object.keys(tileflowModelParts).length;
25324
25376
  var roleCount = Object.keys(tileflowModelLightingRoles).length;
25325
25377
  var modelId2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
25326
25378
  var [glbMagic, jsonChunk, binChunk] = [1179937895, 1313821514, 5130562];
@@ -25378,7 +25430,13 @@ function encodeTileflowModels(models, compressor) {
25378
25430
  return bufferViews.length - 1;
25379
25431
  }
25380
25432
  const mode = vertices ? "ATTRIBUTES" : "TRIANGLES";
25381
- const packed = compressor.encodeGltfBuffer(bytes, count, stride, mode);
25433
+ const packed = compressor.encodeGltfBuffer(
25434
+ bytes,
25435
+ count,
25436
+ stride,
25437
+ mode,
25438
+ vertices ? 1 : void 0
25439
+ );
25382
25440
  const byteOffset = addBlob(packed);
25383
25441
  const fallbackOffset = fallbackLength;
25384
25442
  fallbackLength += bytes.byteLength + (4 - bytes.byteLength % 4) % 4;
@@ -25459,20 +25517,33 @@ function encodeTileflowModels(models, compressor) {
25459
25517
  let surface;
25460
25518
  if (mesh.surface && level.attributes) {
25461
25519
  const attributes = level.attributes;
25462
- const data = new Uint8Array(
25463
- attributes.buffer,
25464
- attributes.byteOffset,
25465
- attributes.byteLength
25466
- );
25467
- const view = addView(data, level.count, 32, true);
25468
25520
  const uv = accessors.length;
25521
+ const rest = new Float32Array(level.count * 6);
25522
+ const second = new Uint16Array(level.count * 2);
25523
+ for (let vertex = 0; vertex < level.count; vertex++) {
25524
+ const at = vertex * 8;
25525
+ rest.set([attributes[at], attributes[at + 1]], vertex * 6);
25526
+ rest.set(attributes.subarray(at + 4, at + 8), vertex * 6 + 2);
25527
+ second[vertex * 2] = Math.round(attributes[at + 2] * 65535);
25528
+ second[vertex * 2 + 1] = Math.round(attributes[at + 3] * 65535);
25529
+ }
25530
+ const view = addView(new Uint8Array(rest.buffer), level.count, 24, true);
25531
+ const lightmap = addView(new Uint8Array(second.buffer), level.count, 4, true);
25469
25532
  accessors.push(
25470
- { bufferView: view, byteOffset: 0, componentType: 5126, count: level.count, type: "VEC4" },
25471
- { bufferView: view, byteOffset: 16, componentType: 5126, count: level.count, type: "VEC4" }
25533
+ { bufferView: view, byteOffset: 0, componentType: 5126, count: level.count, type: "VEC2" },
25534
+ {
25535
+ bufferView: lightmap,
25536
+ byteOffset: 0,
25537
+ componentType: 5123,
25538
+ normalized: true,
25539
+ count: level.count,
25540
+ type: "VEC2"
25541
+ },
25542
+ { bufferView: view, byteOffset: 8, componentType: 5126, count: level.count, type: "VEC4" }
25472
25543
  );
25473
25544
  surface = {
25474
- version: 1,
25475
- attributes: [uv, uv + 1],
25545
+ version: 2,
25546
+ attributes: [uv, uv + 1, uv + 2],
25476
25547
  materials: mesh.surface.materials,
25477
25548
  textures: mesh.surface.textures.map(({ bytes, ...texture }) => ({
25478
25549
  ...texture,
@@ -25489,7 +25560,7 @@ function encodeTileflowModels(models, compressor) {
25489
25560
  mesh: meshes.length - 1,
25490
25561
  translation: [...level.centre],
25491
25562
  scale: [level.step, level.step, level.step],
25492
- ...surface ? { extras: { tileflow: { surface } } } : {}
25563
+ extras: { tileflow: { parts: [...partNamesOf(mesh)], ...surface ? { surface } : {} } }
25493
25564
  });
25494
25565
  }
25495
25566
  roots.push(nodes.length);
@@ -25559,7 +25630,7 @@ async function decodeTileflowModels(buffer, decompressor) {
25559
25630
  if (source !== 0 || byteStride !== stride || count * byteStride !== length)
25560
25631
  throw new Error();
25561
25632
  if (filter !== void 0 && filter !== "NONE") throw new Error();
25562
- if (mode !== (stride > 4 ? "ATTRIBUTES" : "TRIANGLES")) throw new Error();
25633
+ if (mode !== (view.target === 34963 ? "TRIANGLES" : "ATTRIBUTES")) throw new Error();
25563
25634
  out = new Uint8Array(length);
25564
25635
  decoder.decodeGltfBuffer(out, count, byteStride, slice(bin, byteOffset, byteLength), mode);
25565
25636
  } else {
@@ -25598,6 +25669,7 @@ async function decodeTileflowModels(buffer, decompressor) {
25598
25669
  }
25599
25670
  levels.push({
25600
25671
  vertices: level.vertices,
25672
+ partNames: level.partNames,
25601
25673
  indices: level.indices,
25602
25674
  centre: level.centre,
25603
25675
  step: level.step,
@@ -25752,6 +25824,7 @@ function quantise(mesh, compressor) {
25752
25824
  ...low.map((value, axis) => Math.max(centre[axis] - value, high[axis] - centre[axis]))
25753
25825
  );
25754
25826
  const step2 = Math.max(reach, 1e-3) / steps;
25827
+ const names = partNamesOf(mesh);
25755
25828
  const raw = new Uint8Array(count * tileflowModelVertexBytes);
25756
25829
  const view = new DataView(raw.buffer);
25757
25830
  for (let vertex = 0; vertex < count; vertex += 1) {
@@ -25773,12 +25846,12 @@ function quantise(mesh, compressor) {
25773
25846
  for (let axis = 0; axis < 3; axis += 1)
25774
25847
  view.setInt8(at + 8 + axis, clamp3(Math.round(normal[axis] * 127), -127, 127));
25775
25848
  const part = mesh.parts[vertex];
25776
- if (!Number.isInteger(part) || part < 0 || part >= partCount2)
25849
+ if (!Number.isInteger(part) || part < 0 || part >= names.length)
25777
25850
  throw new RangeError(`Vertex ${vertex} has no part.`);
25778
25851
  raw[at + 12] = part;
25779
25852
  raw[at + 13] = Math.round((clamp3(mesh.shades[vertex], -1, 1) + 1) / 2 * 255);
25780
25853
  raw[at + 14] = Math.round(clamp3(mesh.occlusion[vertex], 0, 1) * 255);
25781
- const role = mesh.lightingRoles?.[vertex] ?? tileflowModelDefaultLightingRoles[part];
25854
+ const role = mesh.lightingRoles?.[vertex] ?? tileflowModelDefaultLightingRole(names[part]);
25782
25855
  if (!Number.isInteger(role) || role < 0 || role >= roleCount)
25783
25856
  throw new RangeError(`Vertex ${vertex} has no lighting role.`);
25784
25857
  raw[at + 15] = role;
@@ -25811,6 +25884,24 @@ function quantise(mesh, compressor) {
25811
25884
  order = new Array(unique);
25812
25885
  for (const [index, vertex] of kept.entries())
25813
25886
  if (remap[index] !== 4294967295) order[remap[index]] = vertex;
25887
+ } else if (compressor) {
25888
+ for (const { from, count: length } of mesh.surface.ranges) {
25889
+ const range2 = indices.slice(from, from + length);
25890
+ const [remap, unique] = compressor.reorderMesh(range2, true, false);
25891
+ const global = new Uint32Array(unique);
25892
+ for (const [vertex, local] of remap.entries())
25893
+ if (local !== 4294967295) global[local] = vertex;
25894
+ for (const [at, local] of range2.entries()) indices[from + at] = global[local];
25895
+ }
25896
+ const renumber = new Uint32Array(kept.length).fill(4294967295);
25897
+ order = [];
25898
+ for (const [at, vertex] of indices.entries()) {
25899
+ if (renumber[vertex] === 4294967295) {
25900
+ renumber[vertex] = order.length;
25901
+ order.push(kept[vertex]);
25902
+ }
25903
+ indices[at] = renumber[vertex];
25904
+ }
25814
25905
  }
25815
25906
  const vertices = new Uint8Array(order.length * tileflowModelVertexBytes);
25816
25907
  const attributes = mesh.surface ? new Float32Array(order.length * 8) : void 0;
@@ -25861,6 +25952,10 @@ function checkModel(model) {
25861
25952
  throw new RangeError(`Every vertex of level ${at} needs a part, a shade and a light.`);
25862
25953
  if (level.lightingRoles && level.lightingRoles.length !== count)
25863
25954
  throw new RangeError(`Every vertex of level ${at} needs a lighting role.`);
25955
+ if (!isPartNames(partNamesOf(level)))
25956
+ throw new RangeError(
25957
+ `Level ${at} names 1 to ${tileflowModelLimits.parts} different parts, each in lower-case words joined by hyphens or underscores.`
25958
+ );
25864
25959
  if (level.surface) checkTileflowModelSurface(level.surface, count, level.indices.length);
25865
25960
  for (let index = 0; index < count; index += 1)
25866
25961
  for (let axis = 0; axis < 3; axis += 1) {
@@ -25949,6 +26044,9 @@ function readLevelNode(json, node2, name, viewBytes, hasLightingRoles) {
25949
26044
  const indexCount = index?.count;
25950
26045
  if (!width || index.type !== "SCALAR" || (index.byteOffset ?? 0) !== 0 || !Number.isInteger(indexCount) || indexCount < 3 || indexCount % 3 !== 0 || indexCount / 3 > tileflowModelLimits.triangles)
25951
26046
  throw new Error();
26047
+ const own = node2.extras?.tileflow?.parts;
26048
+ const partNames = own === void 0 ? tileflowModelArchitecturalPartNames : own;
26049
+ if (!isPartNames(partNames)) throw new Error();
25952
26050
  const vertices = viewBytes(
25953
26051
  position.bufferView,
25954
26052
  tileflowModelVertexBytes,
@@ -25957,8 +26055,8 @@ function readLevelNode(json, node2, name, viewBytes, hasLightingRoles) {
25957
26055
  for (let vertex = 0; vertex < count; vertex += 1) {
25958
26056
  const at = vertex * tileflowModelVertexBytes;
25959
26057
  const part = vertices[at + 12];
25960
- if (part >= partCount2) throw new Error();
25961
- if (!hasLightingRoles) vertices[at + 15] = tileflowModelDefaultLightingRoles[part];
26058
+ if (part >= partNames.length) throw new Error();
26059
+ if (!hasLightingRoles) vertices[at + 15] = tileflowModelDefaultLightingRole(partNames[part]);
25962
26060
  else if (vertices[at + 15] >= roleCount) throw new Error();
25963
26061
  }
25964
26062
  const indexBytes = viewBytes(index.bufferView, width, indexCount * width);
@@ -25967,18 +26065,30 @@ function readLevelNode(json, node2, name, viewBytes, hasLightingRoles) {
25967
26065
  const physical = node2.extras?.tileflow?.surface;
25968
26066
  let surface;
25969
26067
  if (physical !== void 0) {
25970
- if (physical?.version !== 1 || !Array.isArray(physical.attributes) || physical.attributes.length !== 2)
26068
+ if (physical?.version !== 2 || !Array.isArray(physical.attributes) || physical.attributes.length !== 3)
25971
26069
  throw new Error();
25972
- const uv = accessor(physical.attributes[0]);
25973
- const colour = accessor(physical.attributes[1]);
25974
- if (!uv || !colour || (uv.byteOffset ?? 0) !== 0 || colour.byteOffset !== 16 || uv.bufferView !== colour.bufferView || [uv, colour].some(
25975
- (value) => value.componentType !== 5126 || value.type !== "VEC4" || value.count !== count || value.normalized === true
25976
- ))
26070
+ const [uv, lightmap, colour] = physical.attributes.map(accessor);
26071
+ if (!uv || !lightmap || !colour || (uv.byteOffset ?? 0) !== 0 || colour.byteOffset !== 8 || (lightmap.byteOffset ?? 0) !== 0 || uv.bufferView !== colour.bufferView || uv.bufferView === lightmap.bufferView || uv.componentType !== 5126 || uv.type !== "VEC2" || colour.componentType !== 5126 || colour.type !== "VEC4" || lightmap.componentType !== 5123 || lightmap.type !== "VEC2" || lightmap.normalized !== true || [uv, lightmap, colour].some((value) => value.count !== count))
25977
26072
  throw new Error();
25978
- const attributeBytes = viewBytes(uv.bufferView, 32, count * 32);
26073
+ const restBytes = viewBytes(uv.bufferView, 24, count * 24);
26074
+ const lightmapBytes = viewBytes(lightmap.bufferView, 4, count * 4);
26075
+ const rest = new Float32Array(
26076
+ restBytes.buffer.slice(restBytes.byteOffset, restBytes.byteOffset + count * 24)
26077
+ );
26078
+ const second = new Uint16Array(
26079
+ lightmapBytes.buffer.slice(lightmapBytes.byteOffset, lightmapBytes.byteOffset + count * 4)
26080
+ );
26081
+ const attributes = new Float32Array(count * 8);
26082
+ for (let vertex = 0; vertex < count; vertex++) {
26083
+ const at = vertex * 8;
26084
+ attributes[at] = rest[vertex * 6];
26085
+ attributes[at + 1] = rest[vertex * 6 + 1];
26086
+ attributes[at + 2] = second[vertex * 2] / 65535;
26087
+ attributes[at + 3] = second[vertex * 2 + 1] / 65535;
26088
+ attributes.set(rest.subarray(vertex * 6 + 2, vertex * 6 + 6), at + 4);
26089
+ }
25979
26090
  if (!Array.isArray(physical.textures) || physical.textures.length > 32) throw new Error();
25980
- surface = {
25981
- attributes: new Float32Array(attributeBytes.buffer, attributeBytes.byteOffset, count * 8),
26091
+ const read = {
25982
26092
  materials: physical.materials,
25983
26093
  ranges: physical.ranges,
25984
26094
  textures: physical.textures.map((texture) => {
@@ -25989,10 +26099,12 @@ function readLevelNode(json, node2, name, viewBytes, hasLightingRoles) {
25989
26099
  }),
25990
26100
  ...physical.emitters === void 0 ? {} : { emitters: physical.emitters }
25991
26101
  };
25992
- checkTileflowModelSurface(surface, count, indexCount);
26102
+ checkTileflowModelSurface({ ...read, attributes }, count, indexCount);
26103
+ surface = { ...read, uv: column(attributes, 0), rgba: column(attributes, 4) };
25993
26104
  }
25994
26105
  return {
25995
26106
  vertices,
26107
+ partNames: [...partNames],
25996
26108
  indices,
25997
26109
  centre: [centre[0], centre[1], centre[2]],
25998
26110
  step: scale[0],
@@ -26001,6 +26113,24 @@ function readLevelNode(json, node2, name, viewBytes, hasLightingRoles) {
26001
26113
  surface
26002
26114
  };
26003
26115
  }
26116
+ function column(attributes, first) {
26117
+ const out = attributes.slice(first, first + 4);
26118
+ for (let at = 8 + first; at < attributes.length; at += 8)
26119
+ for (let component = 0; component < 4; component += 1)
26120
+ if (attributes[at + component] !== out[component]) {
26121
+ const all2 = new Float32Array(attributes.length / 2);
26122
+ for (let vertex = 0; vertex < all2.length / 4; vertex += 1)
26123
+ all2.set(attributes.subarray(vertex * 8 + first, vertex * 8 + first + 4), vertex * 4);
26124
+ return all2;
26125
+ }
26126
+ return out;
26127
+ }
26128
+ function partNamesOf(mesh) {
26129
+ return mesh.partNames ?? tileflowModelArchitecturalPartNames;
26130
+ }
26131
+ function isPartNames(names) {
26132
+ return Array.isArray(names) && names.length >= 1 && names.length <= tileflowModelLimits.parts && names.every(isTileflowModelPartName) && new Set(names).size === names.length;
26133
+ }
26004
26134
  function readGlb(bytes) {
26005
26135
  if (bytes.byteLength < 20 || bytes.byteLength > tileflowModelLimits.bytes) throw new Error();
26006
26136
  const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
@@ -26150,8 +26280,7 @@ function validFootprint(footprint) {
26150
26280
  }
26151
26281
  function readTileflowLandmarkRegion(json) {
26152
26282
  if (!isRecord18(json)) return void 0;
26153
- const named = json.format === "tileflow-landmark-region-v2";
26154
- if (!named && json.format !== "tileflow-landmark-region-v1") return void 0;
26283
+ if (json.format !== "tileflow-landmark-region-v2") return void 0;
26155
26284
  const region = json.region;
26156
26285
  if (json.zoom !== tileflowLandmarkTileZoom || !Array.isArray(region) || region.length !== 3 || region[0] !== tileflowLandmarkRegionZoom || !Array.isArray(json.tiles))
26157
26286
  return void 0;
@@ -26159,17 +26288,13 @@ function readTileflowLandmarkRegion(json) {
26159
26288
  if (!Number.isInteger(x) || !Number.isInteger(y)) return void 0;
26160
26289
  const tiles = [];
26161
26290
  for (const tile of json.tiles) {
26162
- if (!Array.isArray(tile) || tile.length !== (named ? 4 : 3)) return void 0;
26291
+ if (!Array.isArray(tile) || tile.length !== 4) return void 0;
26163
26292
  const [tx, ty, hash, listed] = tile;
26164
26293
  if (!Number.isInteger(tx) || !Number.isInteger(ty) || typeof hash !== "string")
26165
26294
  return void 0;
26166
26295
  if (!sha256.test(hash)) return void 0;
26167
26296
  const [rx, ry] = tileflowLandmarkRegionOf([tx, ty]);
26168
26297
  if (rx !== x || ry !== y) return void 0;
26169
- if (!named) {
26170
- tiles.push([tx, ty, hash]);
26171
- continue;
26172
- }
26173
26298
  const landmarks = readLandmarks(listed, tx, ty);
26174
26299
  if (!landmarks) return void 0;
26175
26300
  tiles.push([tx, ty, hash, landmarks]);
@@ -26298,6 +26423,7 @@ export {
26298
26423
  isTileflowCompilationPhase,
26299
26424
  isTileflowIconSetSource,
26300
26425
  isTileflowLocalDirectory,
26426
+ isTileflowModelPartName,
26301
26427
  isTileflowPortableId,
26302
26428
  isTileflowThemeName,
26303
26429
  isTileflowTimeOfDay,
@@ -26473,8 +26599,9 @@ export {
26473
26599
  tileflowMapDefaultMaxDepth,
26474
26600
  tileflowMapIdSchema,
26475
26601
  tileflowMaxPitch,
26602
+ tileflowModelArchitecturalPartNames,
26476
26603
  tileflowModelContentType,
26477
- tileflowModelDefaultLightingRoles,
26604
+ tileflowModelDefaultLightingRole,
26478
26605
  tileflowModelFileName,
26479
26606
  tileflowModelImageSize,
26480
26607
  tileflowModelLightingRoles,
@@ -586,6 +586,8 @@ type TileflowPoiModuleConfig = {
586
586
  density?: TileflowPoiDensity;
587
587
  /** Category overrides of density, with the same zoom-dependent detail allowance. */
588
588
  densityByCategory?: Partial<Record<TileflowPoiCategory, TileflowPoiDensity>>;
589
+ /** Fixed maximum World filter rank per POI type (0–5), independent of zoom detail. */
590
+ maxRankByType?: Readonly<Record<string, 0 | 1 | 2 | 3 | 4 | 5>>;
589
591
  /** Zoom for the second extra rank of POI detail. Must exceed 16; defaults to 17. */
590
592
  detailZoom?: number;
591
593
  icons?: boolean;
@@ -1,5 +1,5 @@
1
- import { T as TileflowRuntimeManifest } from './manifest-types-DnhSJjCM.js';
2
- export { a as TileflowRuntimeColorScheme, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, d as TileflowRuntimeSystemThemes, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, t as tileflowRuntimeManifestLimits, g as tileflowRuntimeManifestVersion, h as tileflowTimeOfDayManifestVersion } from './manifest-types-DnhSJjCM.js';
1
+ import { T as TileflowRuntimeManifest } from './manifest-types-B0lQjabX.js';
2
+ export { a as TileflowRuntimeColorScheme, b as TileflowRuntimeManifestMapEntry, c as TileflowRuntimeManifestTheme, d as TileflowRuntimeSystemThemes, e as TileflowTimeOfDay, f as TileflowTimeOfDayVariants, t as tileflowRuntimeManifestLimits, g as tileflowRuntimeManifestVersion, h as tileflowTimeOfDayManifestVersion } from './manifest-types-B0lQjabX.js';
3
3
  import { z } from 'zod';
4
4
 
5
5
  declare const tileflowRuntimeManifestSchema: z.ZodType<TileflowRuntimeManifest>;