@tileflow/core 0.1.0-alpha.36 → 0.1.0-alpha.37

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/browser.js CHANGED
@@ -4269,23 +4269,23 @@ function matrix3d(matrix) {
4269
4269
  var tileflowAtmosphereMetadataKey = "tileflow:atmosphere";
4270
4270
  function readTileflowAtmosphere(value) {
4271
4271
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
4272
- const record = value;
4273
- if (record.version !== 1 || Object.keys(record).length !== 7) return void 0;
4272
+ const record2 = value;
4273
+ if (record2.version !== 1 || Object.keys(record2).length !== 7) return void 0;
4274
4274
  for (const key of ["skyColor", "horizonColor", "fogColor", "spaceColor"]) {
4275
- if (typeof record[key] !== "string" || !/^#[0-9a-f]{6}$/i.test(record[key])) return void 0;
4275
+ if (typeof record2[key] !== "string" || !/^#[0-9a-f]{6}$/i.test(record2[key])) return void 0;
4276
4276
  }
4277
4277
  for (const key of ["starIntensity", "starParallax"]) {
4278
- const number = record[key];
4278
+ const number = record2[key];
4279
4279
  if (typeof number !== "number" || !Number.isFinite(number) || number < 0 || number > 1)
4280
4280
  return void 0;
4281
4281
  }
4282
4282
  return {
4283
- skyColor: record.skyColor,
4284
- horizonColor: record.horizonColor,
4285
- fogColor: record.fogColor,
4286
- spaceColor: record.spaceColor,
4287
- starIntensity: record.starIntensity,
4288
- starParallax: record.starParallax
4283
+ skyColor: record2.skyColor,
4284
+ horizonColor: record2.horizonColor,
4285
+ fogColor: record2.fogColor,
4286
+ spaceColor: record2.spaceColor,
4287
+ starIntensity: record2.starIntensity,
4288
+ starParallax: record2.starParallax
4289
4289
  };
4290
4290
  }
4291
4291
  function atmosphereSpaceOpacity(zoom) {
@@ -4832,6 +4832,16 @@ var tileflowModelParts = Object.freeze({
4832
4832
  accent: 3,
4833
4833
  roof: 4
4834
4834
  });
4835
+ var tileflowModelLightingRoles = Object.freeze({
4836
+ unclassified: 0,
4837
+ wall: 1,
4838
+ door: 2,
4839
+ roof: 3,
4840
+ window: 4,
4841
+ lamp: 5,
4842
+ logo: 6
4843
+ });
4844
+ var tileflowModelDefaultLightingRoles = Object.freeze([1, 1, 4, 1, 3]);
4835
4845
  var tileflowModelLimits = Object.freeze({
4836
4846
  /** Levels of detail, nearest first. */
4837
4847
  levels: 3,
@@ -4954,6 +4964,83 @@ function tileMetresAt(latitude, zoom) {
4954
4964
  return 40075016686e-3 * Math.cos(latitude * Math.PI / 180) / 2 ** zoom;
4955
4965
  }
4956
4966
 
4967
+ // src/building-illumination.ts
4968
+ var defaultBuildingFloodLight = Object.freeze({
4969
+ intensity: 0,
4970
+ brightnessScale: 1,
4971
+ color: "#f5cfa8",
4972
+ groundRadius: [30, 100],
4973
+ minGroundHeight: 30,
4974
+ minWallHeight: 200,
4975
+ wallHeightRatio: 1 / 3
4976
+ });
4977
+ var defaultModelEmissionGradient = [0, 1, 1, 1, 0];
4978
+ function readBuildingFloodLight(value) {
4979
+ if (!record(value)) return void 0;
4980
+ const defaults = defaultBuildingFloodLight;
4981
+ const radius = value.groundRadius;
4982
+ return {
4983
+ intensity: bounded(value.intensity, 0, 1, 0),
4984
+ brightnessScale: bounded(value.brightnessScale, 0, 16, 1),
4985
+ color: typeof value.color === "string" ? value.color : defaults.color,
4986
+ groundRadius: Array.isArray(radius) && radius.length === 2 && radius.every((v2) => typeof v2 === "number" && Number.isFinite(v2) && v2 >= 0 && v2 <= 2048) && radius[0] <= radius[1] ? [radius[0], radius[1]] : defaults.groundRadius,
4987
+ minGroundHeight: bounded(value.minGroundHeight, 0, 6e3, defaults.minGroundHeight),
4988
+ minWallHeight: bounded(value.minWallHeight, 0, 6e3, defaults.minWallHeight),
4989
+ wallHeightRatio: bounded(value.wallHeightRatio, 0, 1, defaults.wallHeightRatio)
4990
+ };
4991
+ }
4992
+ function readModelRoleLighting(value) {
4993
+ if (!record(value)) return void 0;
4994
+ const result = {};
4995
+ for (const role of Object.keys(tileflowModelLightingRoles)) {
4996
+ const entry = value[role];
4997
+ if (!record(entry)) continue;
4998
+ const gradient = entry.emissiveHeight;
4999
+ result[role] = {
5000
+ emissiveStrength: bounded(entry.emissiveStrength, 0, 5, 0),
5001
+ ...entry.emissiveVariation === void 0 ? {} : { emissiveVariation: bounded(entry.emissiveVariation, 0, 1, 0) },
5002
+ ...typeof entry.emissiveColor === "string" ? { emissiveColor: entry.emissiveColor } : {},
5003
+ ...typeof entry.color === "string" ? { color: entry.color } : {},
5004
+ ...entry.colorMix === void 0 ? {} : { colorMix: bounded(entry.colorMix, 0, 1, 1) },
5005
+ ...entry.metallic === void 0 ? {} : { metallic: bounded(entry.metallic, 0, 1, 0) },
5006
+ ...entry.roughness === void 0 ? {} : { roughness: bounded(entry.roughness, 0, 1, 1) },
5007
+ ...readRoleColorVariation(entry.colorVariation),
5008
+ emissiveHeight: Array.isArray(gradient) && gradient.length === 5 && gradient.every(
5009
+ (v2, i2) => typeof v2 === "number" && Number.isFinite(v2) && v2 <= 1 && v2 >= (i2 === 4 ? -1 : 0)
5010
+ ) ? gradient : defaultModelEmissionGradient
5011
+ };
5012
+ }
5013
+ return Object.keys(result).length ? result : void 0;
5014
+ }
5015
+ function readRoleColorVariation(value) {
5016
+ if (!record(value)) return {};
5017
+ for (const [key, max2] of [
5018
+ ["hue", 360],
5019
+ ["saturation", 100],
5020
+ ["lightness", 100]
5021
+ ]) {
5022
+ const range = value[key];
5023
+ if (!Array.isArray(range) || range.length !== 2 || range[0] > range[1] || !range.every((v2) => typeof v2 === "number" && Number.isFinite(v2) && v2 >= 0 && v2 <= max2))
5024
+ return {};
5025
+ }
5026
+ if (value.space !== void 0 && value.space !== "srgb" && value.space !== "linear") return {};
5027
+ return { colorVariation: value };
5028
+ }
5029
+ function buildingFloodLightShare(sun, brightnessScale = 1) {
5030
+ const luminance2 = (rgb) => {
5031
+ const linear = rgb.map((c) => c <= 0.03928 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4);
5032
+ return 0.2126 * linear[0] + 0.7152 * linear[1] + 0.0722 * linear[2];
5033
+ };
5034
+ const brightness = (luminance2(sun.ambient.rgb) * sun.ambient.intensity + luminance2(sun.rgb) * sun.intensity * Math.max(0, sun.altitude / 90)) / 2;
5035
+ return Math.min(1, Math.max(0, (0.026 - brightness * brightnessScale) / (0.026 - 0.015)));
5036
+ }
5037
+ function bounded(value, min, max2, fallback) {
5038
+ return typeof value === "number" && Number.isFinite(value) && value >= min && value <= max2 ? value : fallback;
5039
+ }
5040
+ function record(value) {
5041
+ return typeof value === "object" && value !== null && !Array.isArray(value);
5042
+ }
5043
+
4957
5044
  // src/buildings-3d-design.ts
4958
5045
  var tileflowBuildingsMetadataKey = "tileflow:buildings";
4959
5046
  var tileflowDefaultBuildings3DDesign = Object.freeze({
@@ -4968,7 +5055,8 @@ var tileflowDefaultBuildings3DDesign = Object.freeze({
4968
5055
  nightLight: 0,
4969
5056
  windows: false,
4970
5057
  shadows: true,
4971
- occlusion: true
5058
+ occlusion: true,
5059
+ modelOcclusion: 0.75
4972
5060
  });
4973
5061
  function readTileflowBuildings3D(metadata) {
4974
5062
  if (!isRecord6(metadata)) return void 0;
@@ -4980,6 +5068,8 @@ function readTileflowBuildings3D(metadata) {
4980
5068
  if (typeof color !== "string" && !Array.isArray(color)) return void 0;
4981
5069
  const within = (number, high, fallback) => typeof number === "number" && number >= 0 && number <= high ? number : fallback;
4982
5070
  const defaults = tileflowDefaultBuildings3DDesign;
5071
+ const floodLight = readBuildingFloodLight(value.floodLight);
5072
+ const modelLighting = readModelRoleLighting(value.modelLighting);
4983
5073
  return {
4984
5074
  source,
4985
5075
  layers: {
@@ -4994,9 +5084,12 @@ function readTileflowBuildings3D(metadata) {
4994
5084
  verticalGradient: value.verticalGradient !== false,
4995
5085
  ...typeof value.edgeColor === "string" ? { edgeColor: value.edgeColor } : {},
4996
5086
  nightLight: within(value.nightLight, 2, 0),
5087
+ ...floodLight ? { floodLight } : {},
5088
+ ...modelLighting ? { modelLighting } : {},
4997
5089
  windows: value.windows === true,
4998
5090
  shadows: typeof value.shadows === "boolean" ? value.shadows : defaults.shadows,
4999
5091
  occlusion: typeof value.occlusion === "boolean" ? value.occlusion : defaults.occlusion,
5092
+ modelOcclusion: within(value.modelOcclusion, 1, defaults.modelOcclusion),
5000
5093
  ...modelsOf(value.models),
5001
5094
  ...modelColorsOf(value.modelColors),
5002
5095
  ...landmarksOf(value.landmarks)
@@ -5004,6 +5097,17 @@ function readTileflowBuildings3D(metadata) {
5004
5097
  }
5005
5098
  var modelId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
5006
5099
  var tileflowBuildingModelPlacementLimit = 256;
5100
+ var teamModelPrefix = "tileflow://team-models/";
5101
+ function tileflowTeamModelUrl(model) {
5102
+ if (!modelId.test(model) || model.length > 64)
5103
+ throw new RangeError(`"${model}" is not a 3D model name.`);
5104
+ return `${teamModelPrefix}${model}`;
5105
+ }
5106
+ function readTileflowTeamModelName(url) {
5107
+ if (!url.startsWith(teamModelPrefix)) return void 0;
5108
+ const model = url.slice(teamModelPrefix.length);
5109
+ return modelId.test(model) && model.length <= 64 ? model : void 0;
5110
+ }
5007
5111
  function modelsOf(value) {
5008
5112
  if (!Array.isArray(value)) return {};
5009
5113
  const models = [];
@@ -6028,8 +6132,9 @@ function meshTileflowBuildings(layer, options) {
6028
6132
  }
6029
6133
  used = true;
6030
6134
  tallest = Math.max(tallest, height);
6031
- const seed = hash(cx * 0.0731 + cy * 0.19137 + index * 1.618);
6032
- const jitter = rings[0].some((value) => value <= 0 || value >= tileflowBuildingExtent) ? hash(height * 7.31 + base * 3.17 + tone) : seed;
6135
+ const identified = Number.isSafeInteger(feature.id);
6136
+ const seed = identified ? hash(feature.id * 0.0731) : hash(cx * 0.0731 + cy * 0.19137 + index * 1.618);
6137
+ const jitter = !identified && rings[0].some((value) => value <= 0 || value >= tileflowBuildingExtent) ? hash(height * 7.31 + base * 3.17 + tone) : seed;
6033
6138
  const building = { base, top: height, jitter, seed, tone, group, use, cx, cy, index };
6034
6139
  const rounded = rings.map(
6035
6140
  (ring, at) => roundCorners(ring, at, edgeRadius, options.cornerAngle)
@@ -6578,7 +6683,7 @@ function browserWorker() {
6578
6683
  if (typeof Worker === "undefined" || false)
6579
6684
  return;
6580
6685
  const url = URL.createObjectURL(
6581
- new Blob(['"use strict";(()=>{var An=new Set,Dn=!1;function kn(n,e,t=2){let s=e&&e.length,o=s?e[0]*t:n.length;An.size&&An.clear();let r=he(n,0,o,t,!0),i=[];if(!r||r.next===r.prev)return i;let u=0,l=0,c=0;if(s&&(r=_e(n,e,r,t)),n.length>80*t){u=n[0],l=n[1];let a=u,f=l;for(let h=t;h<o;h+=t){let m=n[h],b=n[h+1];m<u&&(u=m),b<l&&(l=b),m>a&&(a=m),b>f&&(f=b)}c=Math.max(a-u,f-l),c=c!==0?32767/c:0}return Wn(r,i,u,l,c),i}function he(n,e,t,s,o){let r=null;if(o===Ye(n,e,t,s)>0)for(let i=e;i<t;i+=s)r=fe(i/s|0,n[i],n[i+1],r);else for(let i=t-s;i>=e;i-=s)r=fe(i/s|0,n[i],n[i+1],r);return r&&In(r,r.next)&&(Tn(r),r=r.next),r}function fn(n,e=n){let t=e===n,s=n,o;do o=!1,s!==s.next&&(An.size===0||!An.has(s))&&(In(s,s.next)||O(s.prev,s,s.next)===0)?((t||s===e)&&(e=s.prev),Dn=!0,Tn(s),s=s.prev,o=!0):(t||s!==e)&&(s=s.next,o=!t);while(o||s!==e);return e}function Wn(n,e,t,s,o){o&&Se(n,t,s,o);let r=n,i=!1;for(;n.prev!==n.next;){let u=n.prev,l=n.next;if(O(u,n,l)<0&&(o?ke(n,t,s,o):Te(n))){e.push(u.i,n.i,l.i),Tn(n),n=l,r=l;continue}if(n=l,n===r){if(Dn=!1,n=fn(n),Dn){r=n;continue}if(!i){n=Be(n,e),r=n,i=!0;continue}Fe(n,e,t,s,o);break}}}function Te(n){let e=n.prev,t=n,s=n.next,o=e.x,r=t.x,i=s.x,u=e.y,l=t.y,c=s.y,a=Math.min(o,r,i),f=Math.min(u,l,c),h=Math.max(o,r,i),m=Math.max(u,l,c),b=s.next;for(;b!==e;){if(b.x>=a&&b.x<=h&&b.y>=f&&b.y<=m&&!(o===b.x&&u===b.y)&&Vn(o,u,r,l,i,c,b.x,b.y)&&O(b.prev,b,b.next)>=0)return!1;b=b.next}return!0}function ke(n,e,t,s){let o=n.prev,r=n,i=n.next,u=o.x,l=r.x,c=i.x,a=o.y,f=r.y,h=i.y,m=Math.min(u,l,c),b=Math.min(a,f,h),d=Math.max(u,l,c),x=Math.max(a,f,h),I=$n(m,b,e,t,s),B=$n(d,x,e,t,s),M=n.prevZ;for(;M&&M.z>=I;){if(M.x>=m&&M.x<=d&&M.y>=b&&M.y<=x&&M!==i&&!(u===M.x&&a===M.y)&&Vn(u,a,l,f,c,h,M.x,M.y)&&O(M.prev,M,M.next)>=0)return!1;M=M.prevZ}let y=n.nextZ;for(;y&&y.z<=B;){if(y.x>=m&&y.x<=d&&y.y>=b&&y.y<=x&&y!==i&&!(u===y.x&&a===y.y)&&Vn(u,a,l,f,c,h,y.x,y.y)&&O(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Be(n,e){let t=n,s=!1;do{let o=t.prev,r=t.next.next;xe(o,t,t.next,r,!1)&&Un(o,r)&&Un(r,o)&&(e.push(o.i,t.i,r.i),Tn(t),Tn(t.next),t=n=r,s=!0),t=t.next}while(t!==n);return s?fn(t):t}function Fe(n,e,t,s,o){let r=n;do{let i=r.next.next;for(;i!==r.prev;){if(r.i!==i.i&&He(r,i)){let u=de(r,i);r=fn(r,r.next),u=fn(u,u.next),Wn(r,e,t,s,o),Wn(u,e,t,s,o);return}i=i.next}r=r.next}while(r!==n)}var Kn=!1;function _e(n,e,t,s){let o=[];for(let r=0,i=e.length;r<i;r++){let u=e[r]*s,l=r<i-1?e[r+1]*s:n.length,c=he(n,u,l,s,!1);c===c.next&&An.add(c),o.push(Ee(c))}o.sort(Pe),Le(n.length/s,e.length),be(t,t),Kn=!0;for(let r=0;r<o.length;r++)t=Oe(o[r],t);return Kn=!1,fn(t)}function Pe(n,e){return n.x-e.x||n.y-e.y||(n.next.y-n.y)/(n.next.x-n.x)-(e.next.y-e.y)/(e.next.x-e.x)}function Oe(n,e){let t=ze(n,e);if(!t)return e;let s=de(t,n),o=s.next;return be(t,o.next),fn(s,s.next),fn(t,t.next)}var me=16,_=new Float64Array(0),Nn=0,Jn=[],Qn=[];function Le(n,e){let t=Math.ceil((n+2*e)/me)+e+2;_.length<t*4&&(_=new Float64Array(t*4)),Nn=0}function be(n,e){let t=n;do{let s=Nn++;Jn[s]=t;let o=t.x,r=t.y,i=t.x,u=t.y,l=0;do{let a=t.next;t.z=s,a.x<o&&(o=a.x),a.x>i&&(i=a.x),a.y<r&&(r=a.y),a.y>u&&(u=a.y),t=a}while(++l<me&&t!==e);Qn[s]=t;let c=s*4;_[c]=o,_[c+1]=r,_[c+2]=i,_[c+3]=u}while(t!==e)}function Ce(n,e){let t=n.z*4;e.x<_[t]&&(_[t]=e.x),e.y<_[t+1]&&(_[t+1]=e.y),e.x>_[t+2]&&(_[t+2]=e.x),e.y>_[t+3]&&(_[t+3]=e.y)}function ce(n){let e=Qn[n];for(;e.prev.next!==e;)e=e.next;return Qn[n]=e,e}function ae(n){let e=Jn[n];for(;e.prev.next!==e;)e=e.next;return Jn[n]=e,e}function ze(n,e){let t=e,s=n.x,o=n.y,r=-1/0,i;if(In(n,t))return t;for(let h=0,m=0;h<Nn;h++,m+=4){if(o<_[m+1]||o>_[m+3]||_[m]>s||_[m+2]<=r)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t){if(In(n,t.next))return t.next;if(o<=t.y&&o>=t.next.y&&t.next.y!==t.y){let d=t.x+(o-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(d<=s&&d>r&&(r=d,i=t.x<t.next.x?t:t.next,d===s))return i}}t=t.next}while(t!==b)}if(!i)return null;let u=i.x,l=i.y,c=Math.min(o,l),a=Math.max(o,l),f=1/0;for(let h=0,m=0;h<Nn;h++,m+=4){if(_[m+2]<u||_[m]>s||_[m+3]<c||_[m+1]>a)continue;let b=ce(h);t=ae(h);do{if(t.prev.next===t&&s>=t.x&&t.x>=u&&s!==t.x&&Vn(o<l?s:r,o,u,l,o<l?r:s,o,t.x,t.y)){let d=Math.abs(o-t.y)/(s-t.x);(Un(t,n)||t.y===o&&t.next.y===o&&t.next.x>s)&&(d<f||d===f&&(t.x>i.x||t.x===i.x&&Ze(i,t)))&&(i=t,f=d)}t=t.next}while(t!==b)}return i}function Ze(n,e){return O(n.prev,n,e.prev)<0&&O(e.next,n,n.next)<0}var K=[],dn=[],an=new Uint32Array(0),vn=new Uint32Array(0),Rn=new Uint32Array(256);function Se(n,e,t,s){let o=n,r=0;do o.z=$n(o.x,o.y,e,t,s),K[r++]=o,o=o.next;while(o!==n);je(r);let i=null;for(let u=0;u<r;u++){let l=K[u];l.prevZ=i,i&&(i.nextZ=l),i=l}i.nextZ=null,K.fill(null,0,r),dn.fill(null,0,r)}function je(n){if(n<=32){for(let e=1;e<n;e++){let t=K[e],s=t.z,o=e-1;for(;o>=0&&K[o].z>s;)K[o+1]=K[o],o--;K[o+1]=t}return}an.length<n&&(an=new Uint32Array(n),vn=new Uint32Array(n),dn=new Array(n));for(let e=0;e<n;e++)an[e]=K[e].z;En(n,K,an,dn,vn,0),En(n,dn,vn,K,an,8),En(n,K,an,dn,vn,16),En(n,dn,vn,K,an,24)}function En(n,e,t,s,o,r){Rn.fill(0);for(let u=0;u<n;u++)Rn[t[u]>>>r&255]++;let i=0;for(let u=0;u<256;u++){let l=Rn[u];Rn[u]=i,i+=l}for(let u=0;u<n;u++){let l=t[u],c=Rn[l>>>r&255]++;s[c]=e[u],o[c]=l}}function $n(n,e,t,s,o){return n=(n-t)*o|0,e=(e-s)*o|0,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n|e<<1}function Ee(n){let e=n,t=n;do(e.x<t.x||e.x===t.x&&e.y<t.y)&&(t=e),e=e.next;while(e!==n);return t}function Vn(n,e,t,s,o,r,i,u){return(o-i)*(e-u)>=(n-i)*(r-u)&&(n-i)*(s-u)>=(t-i)*(e-u)&&(t-i)*(r-u)>=(o-i)*(s-u)}function He(n,e){let t=In(n,e)&&O(n.prev,n,n.next)>0&&O(e.prev,e,e.next)>0;return n.next.i!==e.i&&(t||Un(n,e)&&Un(e,n)&&(O(n.prev,n,e.prev)!==0||O(n,e.prev,e)!==0))&&!Ne(n,e)&&(t||Ve(n,e))}function O(n,e,t){return(e.y-n.y)*(t.x-e.x)-(e.x-n.x)*(t.y-e.y)}function In(n,e){return n.x===e.x&&n.y===e.y}function xe(n,e,t,s,o=!0){let r=O(n,e,t),i=O(n,e,s),u=O(t,s,n),l=O(t,s,e);return(r>0&&i<0||r<0&&i>0)&&(u>0&&l<0||u<0&&l>0)?!0:o?!!(r===0&&Hn(n,t,e)||i===0&&Hn(n,s,e)||u===0&&Hn(t,n,s)||l===0&&Hn(t,e,s)):!1}function Hn(n,e,t){return e.x<=Math.max(n.x,t.x)&&e.x>=Math.min(n.x,t.x)&&e.y<=Math.max(n.y,t.y)&&e.y>=Math.min(n.y,t.y)}function Ne(n,e){let t=Math.min(n.x,e.x),s=Math.max(n.x,e.x),o=Math.min(n.y,e.y),r=Math.max(n.y,e.y),i=n;do{let u=i.next;if(i.x>s&&u.x>s||i.x<t&&u.x<t||i.y>r&&u.y>r||i.y<o&&u.y<o){i=u;continue}if(i.i!==n.i&&u.i!==n.i&&i.i!==e.i&&u.i!==e.i&&xe(i,u,n,e))return!0;i=u}while(i!==n);return!1}function Un(n,e){return O(n.prev,n,n.next)<0?O(n,e,n.next)>=0&&O(n,n.prev,e)>=0:O(n,e,n.prev)<0||O(n,n.next,e)<0}function Ve(n,e){let t=n,s=!1,o=(n.x+e.x)/2,r=(n.y+e.y)/2;do{let i=t.next;t.y>r!=i.y>r&&o<(i.x-t.x)*(r-t.y)/(i.y-t.y)+t.x&&(s=!s),t=i}while(t!==n);return s}function de(n,e){let t=qn(n.i,n.x,n.y),s=qn(e.i,e.x,e.y),o=n.next,r=e.prev;return n.next=e,e.prev=n,t.next=o,o.prev=t,s.next=t,t.prev=s,r.next=s,s.prev=r,s}function fe(n,e,t,s){let o=qn(n,e,t);return s?(o.next=s.next,o.prev=s,s.next.prev=o,s.next=o):(o.prev=o,o.next=o),o}function Tn(n){n.next.prev=n.prev,n.prev.next=n.next,n.prevZ&&(n.prevZ.nextZ=n.nextZ),n.nextZ&&(n.nextZ.prevZ=n.prevZ),Kn&&Ce(n.prev,n.next)}function qn(n,e,t){return{i:n,x:e,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Ye(n,e,t,s){let o=0;for(let r=e,i=t-s;r<t;r+=s)o+=(n[i]-n[r])*(n[r+1]+n[i+1]),i=r;return o}var Xe=.7,Ge=Math.tan(Math.PI/6),De=Math.tan(35*Math.PI/180),We=Math.tan(Math.PI/18),Bn=.65,Fn=.4;function Me(n,e,t,s,o,r,i){let u=String(t.roof_shape??"");if(!u||u==="flat"||u==="sawtooth"||qe(n,i))return;let l=Qe(n);if(!l||Math.abs(ne(n))<Xe*4*l.halfU*l.halfV)return;let c=l.u,a=l.v,f=l.halfU*r,h=l.halfV*r;f<h&&([c,a,f,h]=[a,c,h,f]);let m=ge(t.roof_direction),b=m===void 0?void 0:[Math.sin(m*Math.PI/180),-Math.cos(m*Math.PI/180)];(b!==void 0?Math.abs(b[0]*c[0]+b[1]*c[1])>Math.abs(b[0]*a[0]+b[1]*a[1]):t.roof_orientation==="across")&&u!=="skillion"&&([c,a,f,h]=[a,c,h,f]);let x=Je(u,f,h,b&&[pe(b,c),pe(b,a)]);if(!x)return;let I=Math.max(0,o-s),B=ge(t.roof_height),M,y;if(B!==void 0&&B>0)y=o,M=Math.max(s+Math.min(2,I/2),o-B);else{let p=Math.min(Ke(u,h),I*.6,30);t.height_source==="height"?(y=o,M=o-p):(M=o,y=o+p)}let k=y-M;if(!(k>=.3))return;let[R,N]=l.centre,F=(p,A)=>{let v=(p-R)*r,g=(A-N)*r;return[v*c[0]+g*c[1],v*a[0]+g*a[1]]},S=(p,A)=>[R+(p*c[0]+A*a[0])/r,N+(p*c[1]+A*a[1])/r],J=(p,A)=>{let v=1/0;for(let[g,E,H]of x)v=Math.min(v,g*p+E*A+H);return Math.max(0,v)},Q=(p,A)=>M+k*J(...F(p,A)),L=[];for(let p=0;p+1<e.length;p+=2)L.push(...F(e[p],e[p+1]));ne(L)<0&&nt(L);let C=1.01,$=[-f*C,-h*C,f*C,-h*C,f*C,h*C,-f*C,h*C],V=[];for(let[p,A]of x.entries()){let v=$;for(let[T,j]of x.entries()){if(T===p)continue;let z=A[0]-j[0],nn=A[1]-j[1],en=A[2]-j[2];if(Math.abs(z)+Math.abs(nn)+Math.abs(en)<1e-9){T<p&&(v=[]);continue}if(v=ye(v,z,nn,en),v.length<6)break}if(v.length<6)continue;let g=L,E=v.length/2;for(let T=0;T<E&&g.length>=6;T+=1){let[j,z]=[v[T*2],v[T*2+1]],[nn,en]=[v[(T+1)%E*2],v[(T+1)%E*2+1]];g=ye(g,en-z,j-nn,-(en-z)*j-(j-nn)*z)}if(g.length<6||Math.abs(ne(g))<1e-6)continue;let H=[];for(let T=0;T<g.length;T+=2)H.push(...S(g[T],g[T+1]));let q=kn(H);if(q.length===0)continue;let on=k*A[0],Y=k*A[1],w=on*c[0]+Y*a[0],U=on*c[1]+Y*a[1],rn=Math.hypot(w,U,1);V.push({ring:H,triangles:q,normal:[-w/rn,-U/rn]})}if(V.length!==0)return{eaves:M,ridge:y,faces:V,heightAt:Q,breaks(p,A,v,g){let[E,H]=F(p,A),[q,on]=F(v,g),Y=[];for(let w=0;w<x.length;w+=1)for(let U=w+1;U<x.length;U+=1){let rn=(Z,P)=>(x[w][0]-x[U][0])*Z+(x[w][1]-x[U][1])*P+(x[w][2]-x[U][2]),T=rn(E,H),j=rn(q,on);if(T===j||T*j>0)continue;let z=T/(T-j);if(z<=1e-4||z>=1-1e-4)continue;let nn=E+(q-E)*z,en=H+(on-H)*z;x[w][0]*nn+x[w][1]*en+x[w][2]<=J(nn,en)+1e-6&&Y.push(z)}return[...new Set(Y.map(w=>Math.round(w*1e6)/1e6))].sort((w,U)=>w-U)}}}function Ke(n,e){switch(n){case"skillion":return 2*e*We;case"pyramidal":return e*De;case"gambrel":case"mansard":return e*.8;case"dome":case"onion":case"round":return e;default:return e*Ge}}function Je(n,e,t,s){let o=e,r=t,i=[[0,-1/r,1],[0,1/r,1]],u=c=>c.flatMap(a=>[1,-1].flatMap(f=>{let h=a==="v"?r:o,m=Bn/(Fn*r)*f,b=(1-Bn)/((1-Fn)*r)*f,d=x=>a==="v"?[0,-x]:[-x,0];return[[...d(m),Bn/(Fn*r)*h],[...d(b),Bn+(1-Bn)/((1-Fn)*r)*(h-Fn*r)]]})),l=[.99,.8,.5,.2];switch(n){case"gabled":return i;case"saltbox":{let c=r/3;return[[0,-1/(r-c),1+c/(r-c)],[0,1/(r+c),1-c/(r+c)]]}case"hipped":return[...i,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...i,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...i,[-1/o,0,1],[1/o,0,1]];case"gambrel":return u(["v"]);case"mansard":return u(["v","u"]);case"skillion":{let[c,a]=s??[0,1],f=Math.abs(c)*o+Math.abs(a)*r;return f>0?[[-c/(2*f),-a/(2*f),.5]]:void 0}case"round":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return[1,-1].map(f=>[0,-f*c/(r*a),1/a])})];case"dome":case"onion":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return Array.from({length:12},(f,h)=>{let m=h*Math.PI/6;return[-c*Math.cos(m)/(o*a),-c*Math.sin(m)/(r*a),1/a]})})];default:return}}function ye(n,e,t,s){let o=n.length/2,r=[];for(let i=0;i<o;i+=1){let u=n[i*2],l=n[i*2+1],c=n[(i+1)%o*2],a=n[(i+1)%o*2+1],f=e*u+t*l+s,h=e*c+t*a+s;if(f<=0&&r.push(u,l),f<0&&h>0||f>0&&h<0){let m=f/(f-h);r.push(u+(c-u)*m,l+(a-l)*m)}}return r}function Qe(n){let e=$e(n);if(e.length<3)return;let t,s=1/0;for(let o=0;o<e.length;o+=1){let[r,i]=e[o],[u,l]=e[(o+1)%e.length],c=Math.hypot(u-r,l-i);if(c===0)continue;let a=[(u-r)/c,(l-i)/c],f=[-a[1],a[0]],[h,m,b,d]=[1/0,-1/0,1/0,-1/0];for(let[I,B]of e){let M=I*a[0]+B*a[1],y=I*f[0]+B*f[1];h=Math.min(h,M),m=Math.max(m,M),b=Math.min(b,y),d=Math.max(d,y)}let x=(m-h)*(d-b);if(x<s){s=x;let I=(h+m)/2,B=(b+d)/2;t={centre:[I*a[0]+B*f[0],I*a[1]+B*f[1]],u:a,v:f,halfU:(m-h)/2,halfV:(d-b)/2}}}return t&&t.halfU>0&&t.halfV>0?t:void 0}function $e(n){let e=[];for(let r=0;r+1<n.length;r+=2)e.push([n[r],n[r+1]]);e.sort((r,i)=>r[0]-i[0]||r[1]-i[1]);let t=(r,i,u)=>(i[0]-r[0])*(u[1]-r[1])-(i[1]-r[1])*(u[0]-r[0]),s=[];for(let r of e){for(;s.length>=2&&t(s.at(-2),s.at(-1),r)<=0;)s.pop();s.push(r)}let o=[];for(let r of e.reverse()){for(;o.length>=2&&t(o.at(-2),o.at(-1),r)<=0;)o.pop();o.push(r)}return[...s.slice(0,-1),...o.slice(0,-1)]}function qe(n,e){let[t,s,o,r]=[1/0,-1/0,1/0,-1/0];for(let u=0;u+1<n.length;u+=2)t=Math.min(t,n[u]),s=Math.max(s,n[u]),o=Math.min(o,n[u+1]),r=Math.max(r,n[u+1]);let i=n.length/2;for(let u=0;u<i;u+=1){let l=n[u*2],c=n[u*2+1],a=n[(u+1)%i*2],f=n[(u+1)%i*2+1];if(t<0&&l===t&&a===t||s>e&&l===s&&a===s||o<0&&c===o&&f===o||r>e&&c===r&&f===r)return!0}return!1}function ne(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e/2}function nt(n){let e=[];for(let t=0;t<n.length;t+=2)e.push([n[t],n[t+1]]);e.reverse(),n.splice(0,n.length,...e.flat())}function pe(n,e){return n[0]*e[0]+n[1]*e[1]}function ge(n){let e=typeof n=="string"?Number(n):n;return typeof e=="number"&&Number.isFinite(e)?e:void 0}var gt=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),Mt=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function ee(n,e,t){for(let s of n){let o=!1;for(let r=0,i=s.length-2;r<s.length;i=r,r+=2){let u=s[r],l=s[r+1],c=s[i],a=s[i+1];l>t!=a>t&&e<(c-u)*(t-l)/(a-l)+u&&(o=!o)}if(o)return!0}return!1}function Re(n,e,t){for(let s of e){let[o,r,i,u]=ve(s);for(let l of n){let[c,a,f,h]=ve(l);if(!(c>i-t||f<o+t||a>u-t||h<r+t)&&(we(l,s,t)||we(s,l,t)))return!0}}return!1}function we(n,e,t){let s=(o,r)=>ee([e],o,r)&&tt(e,o,r)>t;for(let o=0;o+1<n.length;o+=2)if(s(n[o],n[o+1]))return!0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=[0,1];for(let f=0,h=e.length-2;f<e.length;h=f,f+=2){let m=et(i,u,l,c,e[h],e[h+1],e[f],e[f+1]);m!==void 0&&a.push(m)}if(a.length!==2){a.sort((f,h)=>f-h);for(let f=1;f<a.length;f+=1){let h=(a[f-1]+a[f])/2;if(s(i+(l-i)*h,u+(c-u)*h))return!0}}}return!1}function et(n,e,t,s,o,r,i,u){let l=t-n,c=s-e,a=i-o,f=u-r,h=l*f-c*a;if(h===0)return;let m=((o-n)*f-(r-e)*a)/h,b=((o-n)*c-(r-e)*l)/h;return m>0&&m<1&&b>=0&&b<=1?m:void 0}function tt(n,e,t){let s=1/0;for(let o=0,r=n.length-2;o<n.length;r=o,o+=2){let[i,u,l,c]=[n[r],n[r+1],n[o],n[o+1]],a=(l-i)**2+(c-u)**2,f=a>0?Math.max(0,Math.min(1,((e-i)*(l-i)+(t-u)*(c-u))/a)):0;s=Math.min(s,Math.hypot(e-(i+(l-i)*f),t-(u+(c-u)*f)))}return s}function ve(n){let[e,t,s,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<n.length;r+=2)e=Math.min(e,n[r]),s=Math.max(s,n[r]),t=Math.min(t,n[r+1]),o=Math.max(o,n[r+1]);return[e,t,s,o]}var tn=8192,te=28,ot={commercial:1,active:2,destination:3},rt={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(n=>[n,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(n=>[n,3]))},st={dining:1,entertainment:2,nightlife:3},oe={...Object.fromEntries(["alcoholic_beverage_venue","bar","beer","biergarten","brewery","casino","cocktail_bar","dance_club","karaoke","lounge","music_venue","night_club","nightclub","pub","wine_bar"].map(n=>[n,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(n=>[n,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(n=>[n,1]))},ut=6,it=.5,lt=.5;function Ue(n,e){let t=tn/n.extent,s=e.tileMetres/tn,o=e.defaultHeight??5,r=Number.isFinite(e.edgeRadius)?Math.max(0,Math.min(1,e.edgeRadius))/s:0,i=new re,u=new Yn,l=new Yn,c=[],a=[],f=0,h=ht(n,e);for(let[y,k]of n.features.entries()){if(k.type!=="Polygon"||c.length>=65535)continue;let R=k.properties,N=R.hide_3d;if(N===!0||N===1||N==="1"||N==="true")continue;let F=Math.min(6e3,Ae(R.render_height)??o),S=Math.min(F,Ae(R.render_min_height)??0),J=ot[String(R.building_tone??"")]??0,Q=rt[String(R.class??"")]??0,L=h.get(y)??0,C=c.length,$=!1;for(let V of mt(k.geometry,t)){let p=V.map(xt).filter(w=>w.length>=6);if(p.length===0||Xn(p[0])===0)continue;let[A,v]=bt(p[0]);if(e.masks?.length&&(ee(e.masks,A,v)||Re(p,e.masks,lt/s)))continue;let g={cell:Math.min(7,Math.max(0,Math.floor(v/1024)))*8+Math.min(7,Math.max(0,Math.floor(A/1024))),bounds:[1/0,1/0,-1/0,-1/0,F+.1],roofsFrom:u.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let w of p)for(let U=0;U<w.length;U+=2)g.bounds[0]=Math.min(g.bounds[0],w[U]-1),g.bounds[1]=Math.min(g.bounds[1],w[U+1]-1),g.bounds[2]=Math.max(g.bounds[2],w[U]+1),g.bounds[3]=Math.max(g.bounds[3],w[U+1]+1);$=!0,f=Math.max(f,F);let E=Ie(A*.0731+v*.19137+C*1.618),H=p[0].some(w=>w<=0||w>=tn)?Ie(F*7.31+S*3.17+J):E,q={base:S,top:F,jitter:H,seed:E,tone:J,group:Q,use:L,cx:A,cy:v,index:C},on=p.map((w,U)=>ft(w,U,r,e.cornerAngle));p=on.map(({ring:w})=>w);let Y=V.length===1&&p.length===1?Me(V[0],p[0],R,S,F,s,tn):void 0;Y?(f=Math.max(f,Y.ridge),g.bounds[4]=Math.max(g.bounds[4],Y.ridge+.1),at(Y,q,i,u)):ct(p,q,i,u);for(let[w,U]of p.entries()){let rn=Xn(U)>0,T=w===0?rn?-1:1:rn?1:-1,j=U.length/2,z=Z=>{let P=(Z%j+j)%j;return[U[P*2],U[P*2+1]]},nn=Z=>{let[P,D]=z(Z-1),[X,G]=z(Z),[un,mn]=z(Z+1);if(hn(P,D,X,G)||hn(X,G,un,mn))return!1;let bn=Math.hypot(X-P,G-D),ln=Math.hypot(un-X,mn-G);if(!bn||!ln)return!1;let _n=-(G-D)/bn*T,Pn=(X-P)/bn*T;return(_n*(un-X)+Pn*(mn-G))/ln>it},en=(Z,P)=>Y?Y.heightAt(Z,P):F,pn=0;for(let Z=0;Z<j;Z+=1){let[P,D]=z(Z),[X,G]=z(Z+1),un=Math.hypot(X-P,G-D);if(un===0||hn(P,D,X,G))continue;let mn=-(G-D)/un*T,bn=(X-P)/un*T,ln=on[w].normals[Z]??[mn,bn],_n=on[w].normals[(Z+1)%j]??[mn,bn],Pn=W=>{let gn=ln[0]+(_n[0]-ln[0])*W,Mn=ln[1]+(_n[1]-ln[1])*W,cn=Math.hypot(gn,Mn)||1;return[gn/cn,Mn/cn]},sn=[0,...Y?Y.breaks(P,D,X,G):[],1];for(let W=0;W+1<sn.length;W+=1){let gn=W===0,Mn=W+2===sn.length,cn=P+(X-P)*sn[W],On=D+(G-D)*sn[W],Ln=P+(X-P)*sn[W+1],Cn=D+(G-D)*sn[W+1],wn=Math.hypot(Ln-cn,Cn-On)/tn*e.tileMetres,zn=(gn&&nn(Z)?1:0)|(Mn&&nn(Z+1)?2:0)|(w>0?4:0),Zn=(gn&&se(P,D)?1:0)|(Mn&&se(X,G)?2:0),Gn=pn+wn,Sn=Pn(sn[W]),jn=Pn(sn[W+1]),ie=en(cn,On),le=en(Ln,Cn),xn=i.write(q,cn,On,0,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie);i.write(q,Ln,Cn,0,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),i.write(q,cn,On,1,Sn[0],Sn[1],1,pn,zn,wn,0,Zn,ie),i.write(q,Ln,Cn,1,jn[0],jn[1],1,Gn,zn,wn,1,Zn,le),l.push(xn,xn+1,xn+2,xn+1,xn+3,xn+2),pn=Gn}}}g.roofsCount=u.length-g.roofsFrom,g.wallsCount=l.length-g.wallsFrom,a.push(g)}$&&c.push(R)}if(i.count===0)return;let m=new Uint32Array(u.length+l.length),b=[],d=0,x=u.length,I=u.values(),B=l.values();a.sort((y,k)=>y.cell-k.cell);let M=-1;for(let y of a){y.cell!==M&&(M=y.cell,b.push({bounds:[...y.bounds],roofsFrom:d,roofsCount:0,wallsFrom:x,wallsCount:0}));let k=b.at(-1);for(let R=0;R<5;R+=1)k.bounds[R]=R<2?Math.min(k.bounds[R],y.bounds[R]):Math.max(k.bounds[R],y.bounds[R]);m.set(I.subarray(y.roofsFrom,y.roofsFrom+y.roofsCount),d),m.set(B.subarray(y.wallsFrom,y.wallsFrom+y.wallsCount),x),d+=y.roofsCount,x+=y.wallsCount,k.roofsCount+=y.roofsCount,k.wallsCount+=y.wallsCount}return{groups:b,vertices:i.bytes(),indices:m,wallsFrom:u.length,top:f,features:c}}function ct(n,e,t,s){let o=[],r=[];for(let l of n)r.push(o.length/2),o.push(...l);r.push(o.length/2);let i=(l,c)=>{let a=r.findIndex((d,x)=>l>=d&&l<r[x+1]),[f,h]=[r[a],r[a+1]-1];if(c<f||c>h)return!1;let[m,b]=l<c?[l,c]:[c,l];return b-m!==1&&!(m===f&&b===h)?!1:!hn(o[l*2],o[l*2+1],o[c*2],o[c*2+1])},u=kn(o,r.slice(1,-1),2);for(let l=0;l+2<u.length;l+=3){let c=[u[l],u[l+1],u[l+2]],a=0;for(let f=0;f<3;f+=1)i(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries())s.push(t.write(e,o[h*2],o[h*2+1],1,0,0,0,0,0,0,f,a))}}function at(n,e,t,s){for(let o of n.faces){let r=o.ring,i=r.length/2,u=(l,c)=>{let[a,f]=l<c?[l,c]:[c,l];return f-a!==1&&!(a===0&&f===i-1)?!1:!hn(r[l*2],r[l*2+1],r[c*2],r[c*2+1])};for(let l=0;l+2<o.triangles.length;l+=3){let c=[o.triangles[l],o.triangles[l+1],o.triangles[l+2]],a=0;for(let f=0;f<3;f+=1)u(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries()){let m=r[h*2],b=r[h*2+1];s.push(t.write(e,m,b,1,o.normal[0],o.normal[1],0,0,0,0,f,a,n.heightAt(m,b)))}}}}function ft(n,e,t,s=Math.PI/6){if(t<1)return{ring:n,normals:[]};let o=n.length/2,r=Xn(n)>0,i=e===0?r?-1:1:r?1:-1,u=[],l=[],c=(a,f,h)=>{a=yn(a),f=yn(f),!(u.at(-2)===a&&u.at(-1)===f)&&(u.push(a,f),l.push(h))};for(let a=0;a<o;a+=1){let f=(a+o-1)%o,h=(a+1)%o,[m,b]=[n[f*2],n[f*2+1]],[d,x]=[n[a*2],n[a*2+1]],[I,B]=[n[h*2],n[h*2+1]],M=Math.hypot(d-m,x-b),y=Math.hypot(I-d,B-x);if(!M||!y||se(d,x)||hn(m,b,d,x)||hn(d,x,I,B)){c(d,x);continue}let[k,R]=[(d-m)/M,(x-b)/M],[N,F]=[(I-d)/y,(B-x)/y],S=Math.atan2(k*F-R*N,k*N+R*F);if(S*i>=-.08||Math.abs(S)>Math.PI*.85){c(d,x);continue}let J=Math.tan(Math.abs(S)/2),Q=Math.min(t*J,M*.2,y*.2),L=Q/J;if(L<1){c(d,x);continue}let[C,$]=[-R*i,k*i],[V,p]=[d-k*Q-C*L,x-R*Q-$*L],A=Math.atan2($,C),v=Math.max(s<=Math.PI/6?2:1,Math.ceil(Math.abs(S)/Math.max(Math.PI/6,Math.min(Math.PI/3,s))));for(let g=0;g<=v;g+=1){let E=A+S*g/v,H=[Math.cos(E),Math.sin(E)];c(V+H[0]*L,p+H[1]*L,H)}}return u[0]===u.at(-2)&&u[1]===u.at(-1)&&(u.splice(-2),l.pop()),u.length>=6?{ring:u,normals:l}:{ring:n,normals:[]}}var re=class{#n=new ArrayBuffer(4096*te);#e=new DataView(this.#n);count=0;write(e,t,s,o,r,i,u,l,c,a,f=0,h=0,m=e.top){let b=te;if((this.count+1)*b>this.#n.byteLength){let I=new ArrayBuffer(this.#n.byteLength*2);new Uint8Array(I).set(new Uint8Array(this.#n)),this.#n=I,this.#e=new DataView(I)}let d=this.#e,x=this.count*b;return d.setUint16(x,yn(t),!0),d.setUint16(x+2,yn(s),!0),d.setUint16(x+4,Math.min(65535,Math.round(e.base*10)),!0),d.setUint16(x+6,Math.min(65535,Math.max(0,Math.round(m*10))),!0),d.setInt8(x+8,Math.round(r*127)),d.setInt8(x+9,Math.round(i*127)),d.setUint8(x+10,o),d.setUint8(x+11,u),d.setUint16(x+12,Math.round(l*10)%65536,!0),d.setUint8(x+14,Math.round(e.jitter*255)),d.setUint8(x+15,Math.round(e.seed*255)),d.setUint16(x+16,yn(e.cx),!0),d.setUint16(x+18,yn(e.cy),!0),d.setUint8(x+20,e.tone+4*e.group),d.setUint8(x+21,e.use),d.setUint8(x+22,c),d.setUint8(x+23,Math.min(255,Math.round(a))),d.setUint16(x+24,e.index,!0),d.setUint8(x+26,f),d.setUint8(x+27,h),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#n.slice(0,this.count*te))}},Yn=class{#n=new Uint32Array(8192);length=0;push(...e){if(this.length+e.length>this.#n.length){let t=new Uint32Array(Math.max(this.length+e.length,this.#n.length*2));t.set(this.#n),this.#n=t}for(let t of e)this.#n[this.length++]=t}values(){return this.#n.subarray(0,this.length)}};function ht(n,e){let t=new Map;if(n.features.some(u=>u.properties.night_use!==void 0)){for(let[u,l]of n.features.entries()){let c=st[String(l.properties.night_use??"")];c&&t.set(u,c)}return t}let s=e.places;if(!s||s.features.length===0)return t;let o=ut/e.tileMetres*n.extent,r=[];for(let[u,l]of n.features.entries()){if(l.type!=="Polygon")continue;let c=l.properties.hide_3d;if(c===!0||c===1||c==="1"||c==="true")continue;let a=[1/0,1/0,-1/0,-1/0];for(let f of l.geometry)for(let h=0;h+1<f.length;h+=2)a[0]=Math.min(a[0],f[h]),a[1]=Math.min(a[1],f[h+1]),a[2]=Math.max(a[2],f[h]),a[3]=Math.max(a[3],f[h+1]);r.push({index:u,rings:l.geometry,box:a})}let i=n.extent/s.extent;for(let u of s.features){if(u.type!=="Point")continue;let l=u.properties,c=oe[String(l.type??"")]??oe[String(l.subclass??"")]??oe[String(l.class??"")];if(c)for(let a of u.geometry)for(let f=0;f+1<a.length;f+=2){let h=a[f]*i,m=a[f+1]*i,b,d,x=o;for(let B of r){let[M,y,k,R]=B.box;if(h<M-o||h>k+o||m<y-o||m>R+o)continue;let N=!1;for(let F of B.rings)for(let S=0,J=F.length-2;S+1<F.length;J=S,S+=2){let Q=F[J],L=F[J+1],C=F[S],$=F[S+1];$>m!=L>m&&h<(Q-C)*(m-$)/(L-$)+C&&(N=!N);let V=C-Q,p=$-L,A=V*V+p*p,v=A?Math.min(1,Math.max(0,((h-Q)*V+(m-L)*p)/A)):0,g=Math.hypot(h-Q-v*V,m-L-v*p);g<x&&(x=g,d=B.index)}if(N){b=B.index;break}}let I=b??d;I!==void 0&&t.set(I,Math.max(t.get(I)??0,c))}}return t}function yn(n){return Math.min(tn,Math.max(0,Math.round(n)))}function Ae(n){let e=Number(n);return Number.isFinite(e)&&e>0?e:void 0}function mt(n,e){let t=[],s;for(let o of n){let r=[];for(let u=0;u+1<o.length;u+=2){let l=o[u]*e,c=o[u+1]*e;r.length&&l===r.at(-2)&&c===r.at(-1)||r.push(l,c)}if(r.length>=6&&r[0]===r.at(-2)&&r[1]===r.at(-1)&&(r.length-=2),r.length<6)continue;let i=Math.sign(Xn(r));i&&(s??=i,i===s||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function Xn(n){let e=0,t=n.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;e+=n[s*2]*n[o*2+1]-n[o*2]*n[s*2+1]}return e}function bt(n){let e=0,t=0,s=n.length/2;for(let o=0;o<s;o+=1)e+=n[o*2],t+=n[o*2+1];return[e/s,t/s]}function se(n,e){return n<=0||e<=0||n>=tn||e>=tn}function hn(n,e,t,s){let o=tn;return n<=0&&t<=0||n>=o&&t>=o||e<=0&&s<=0||e>=o&&s>=o}function xt(n){let e=tn,t=[...n],s=[[0,0,o=>o>=0],[0,e,o=>o<=e],[1,0,o=>o>=0],[1,e,o=>o<=e]];for(let[o,r,i]of s){let u=[],l=t.length/2;for(let c=0;c<l;c+=1){let a=[t[c*2],t[c*2+1]],f=[t[(c+1)%l*2],t[(c+1)%l*2+1]],h=i(a[o]),m=i(f[o]);if(h&&u.push(a[0],a[1]),h!==m){let b=(r-a[o])/(f[o]-a[o]);u.push(a[0]+(f[0]-a[0])*b,a[1]+(f[1]-a[1])*b)}}if(t=u,t.length<6)return[]}return t}function Ie(n){let e=Math.sin(n*12.9898+78.233)*43758.5453;return e-Math.floor(e)}var ue=globalThis;ue.onmessage=({data:n})=>{try{let e=Ue(n.layer,n.options);ue.postMessage({mesh:e},e?[e.vertices.buffer,e.indices.buffer]:[])}catch(e){ue.postMessage({error:String(e)})}};})();\n'], { type: "text/javascript" })
6686
+ new Blob(['"use strict";(()=>{var Re=new Set,De=!1;function Ue(e,n,t=2){let s=n&&n.length,o=s?n[0]*t:e.length;Re.size&&Re.clear();let r=bn(e,0,o,t,!0),u=[];if(!r||r.next===r.prev)return u;let i=0,l=0,c=0;if(s&&(r=Pn(e,n,r,t)),e.length>80*t){i=e[0],l=e[1];let a=i,f=l;for(let h=t;h<o;h+=t){let m=e[h],b=e[h+1];m<i&&(i=m),b<l&&(l=b),m>a&&(a=m),b>f&&(f=b)}c=Math.max(a-i,f-l),c=c!==0?32767/c:0}return We(r,u,i,l,c),u}function bn(e,n,t,s,o){let r=null;if(o===Xn(e,n,t,s)>0)for(let u=n;u<t;u+=s)r=mn(u/s|0,e[u],e[u+1],r);else for(let u=t-s;u>=n;u-=s)r=mn(u/s|0,e[u],e[u+1],r);return r&&Ae(r,r.next)&&(Te(r),r=r.next),r}function ae(e,n=e){let t=n===e,s=e,o;do o=!1,s!==s.next&&(Re.size===0||!Re.has(s))&&(Ae(s,s.next)||P(s.prev,s,s.next)===0)?((t||s===n)&&(n=s.prev),De=!0,Te(s),s=s.prev,o=!0):(t||s!==n)&&(s=s.next,o=!t);while(o||s!==n);return n}function We(e,n,t,s,o){o&&jn(e,t,s,o);let r=e,u=!1;for(;e.prev!==e.next;){let i=e.prev,l=e.next;if(P(i,e,l)<0&&(o?Bn(e,t,s,o):kn(e))){n.push(i.i,e.i,l.i),Te(e),e=l,r=l;continue}if(e=l,e===r){if(De=!1,e=ae(e),De){r=e;continue}if(!u){e=Fn(e,n),r=e,u=!0;continue}_n(e,n,t,s,o);break}}}function kn(e){let n=e.prev,t=e,s=e.next,o=n.x,r=t.x,u=s.x,i=n.y,l=t.y,c=s.y,a=Math.min(o,r,u),f=Math.min(i,l,c),h=Math.max(o,r,u),m=Math.max(i,l,c),b=s.next;for(;b!==n;){if(b.x>=a&&b.x<=h&&b.y>=f&&b.y<=m&&!(o===b.x&&i===b.y)&&Ne(o,i,r,l,u,c,b.x,b.y)&&P(b.prev,b,b.next)>=0)return!1;b=b.next}return!0}function Bn(e,n,t,s){let o=e.prev,r=e,u=e.next,i=o.x,l=r.x,c=u.x,a=o.y,f=r.y,h=u.y,m=Math.min(i,l,c),b=Math.min(a,f,h),d=Math.max(i,l,c),x=Math.max(a,f,h),U=$e(m,b,n,t,s),k=$e(d,x,n,t,s),M=e.prevZ;for(;M&&M.z>=U;){if(M.x>=m&&M.x<=d&&M.y>=b&&M.y<=x&&M!==u&&!(i===M.x&&a===M.y)&&Ne(i,a,l,f,c,h,M.x,M.y)&&P(M.prev,M,M.next)>=0)return!1;M=M.prevZ}let y=e.nextZ;for(;y&&y.z<=k;){if(y.x>=m&&y.x<=d&&y.y>=b&&y.y<=x&&y!==u&&!(i===y.x&&a===y.y)&&Ne(i,a,l,f,c,h,y.x,y.y)&&P(y.prev,y,y.next)>=0)return!1;y=y.nextZ}return!0}function Fn(e,n){let t=e,s=!1;do{let o=t.prev,r=t.next.next;yn(o,t,t.next,r,!1)&&Ie(o,r)&&Ie(r,o)&&(n.push(o.i,t.i,r.i),Te(t),Te(t.next),t=e=r,s=!0),t=t.next}while(t!==e);return s?ae(t):t}function _n(e,n,t,s,o){let r=e;do{let u=r.next.next;for(;u!==r.prev;){if(r.i!==u.i&&Nn(r,u)){let i=pn(r,u);r=ae(r,r.next),i=ae(i,i.next),We(r,n,t,s,o),We(i,n,t,s,o);return}u=u.next}r=r.next}while(r!==e)}var Ke=!1;function Pn(e,n,t,s){let o=[];for(let r=0,u=n.length;r<u;r++){let i=n[r]*s,l=r<u-1?n[r+1]*s:e.length,c=bn(e,i,l,s,!1);c===c.next&&Re.add(c),o.push(Hn(c))}o.sort(On),zn(e.length/s,n.length),dn(t,t),Ke=!0;for(let r=0;r<o.length;r++)t=Ln(o[r],t);return Ke=!1,ae(t)}function On(e,n){return e.x-n.x||e.y-n.y||(e.next.y-e.y)/(e.next.x-e.x)-(n.next.y-n.y)/(n.next.x-n.x)}function Ln(e,n){let t=Sn(e,n);if(!t)return n;let s=pn(t,e),o=s.next;return dn(t,o.next),ae(s,s.next),ae(t,t.next)}var xn=16,_=new Float64Array(0),He=0,Je=[],Qe=[];function zn(e,n){let t=Math.ceil((e+2*n)/xn)+n+2;_.length<t*4&&(_=new Float64Array(t*4)),He=0}function dn(e,n){let t=e;do{let s=He++;Je[s]=t;let o=t.x,r=t.y,u=t.x,i=t.y,l=0;do{let a=t.next;t.z=s,a.x<o&&(o=a.x),a.x>u&&(u=a.x),a.y<r&&(r=a.y),a.y>i&&(i=a.y),t=a}while(++l<xn&&t!==n);Qe[s]=t;let c=s*4;_[c]=o,_[c+1]=r,_[c+2]=u,_[c+3]=i}while(t!==n)}function Cn(e,n){let t=e.z*4;n.x<_[t]&&(_[t]=n.x),n.y<_[t+1]&&(_[t+1]=n.y),n.x>_[t+2]&&(_[t+2]=n.x),n.y>_[t+3]&&(_[t+3]=n.y)}function fn(e){let n=Qe[e];for(;n.prev.next!==n;)n=n.next;return Qe[e]=n,n}function hn(e){let n=Je[e];for(;n.prev.next!==n;)n=n.next;return Je[e]=n,n}function Sn(e,n){let t=n,s=e.x,o=e.y,r=-1/0,u;if(Ae(e,t))return t;for(let h=0,m=0;h<He;h++,m+=4){if(o<_[m+1]||o>_[m+3]||_[m]>s||_[m+2]<=r)continue;let b=fn(h);t=hn(h);do{if(t.prev.next===t){if(Ae(e,t.next))return t.next;if(o<=t.y&&o>=t.next.y&&t.next.y!==t.y){let d=t.x+(o-t.y)*(t.next.x-t.x)/(t.next.y-t.y);if(d<=s&&d>r&&(r=d,u=t.x<t.next.x?t:t.next,d===s))return u}}t=t.next}while(t!==b)}if(!u)return null;let i=u.x,l=u.y,c=Math.min(o,l),a=Math.max(o,l),f=1/0;for(let h=0,m=0;h<He;h++,m+=4){if(_[m+2]<i||_[m]>s||_[m+3]<c||_[m+1]>a)continue;let b=fn(h);t=hn(h);do{if(t.prev.next===t&&s>=t.x&&t.x>=i&&s!==t.x&&Ne(o<l?s:r,o,i,l,o<l?r:s,o,t.x,t.y)){let d=Math.abs(o-t.y)/(s-t.x);(Ie(t,e)||t.y===o&&t.next.y===o&&t.next.x>s)&&(d<f||d===f&&(t.x>u.x||t.x===u.x&&Zn(u,t)))&&(u=t,f=d)}t=t.next}while(t!==b)}return u}function Zn(e,n){return P(e.prev,e,n.prev)<0&&P(n.next,e,e.next)<0}var J=[],de=[],ce=new Uint32Array(0),we=new Uint32Array(0),ve=new Uint32Array(256);function jn(e,n,t,s){let o=e,r=0;do o.z=$e(o.x,o.y,n,t,s),J[r++]=o,o=o.next;while(o!==e);En(r);let u=null;for(let i=0;i<r;i++){let l=J[i];l.prevZ=u,u&&(u.nextZ=l),u=l}u.nextZ=null,J.fill(null,0,r),de.fill(null,0,r)}function En(e){if(e<=32){for(let n=1;n<e;n++){let t=J[n],s=t.z,o=n-1;for(;o>=0&&J[o].z>s;)J[o+1]=J[o],o--;J[o+1]=t}return}ce.length<e&&(ce=new Uint32Array(e),we=new Uint32Array(e),de=new Array(e));for(let n=0;n<e;n++)ce[n]=J[n].z;je(e,J,ce,de,we,0),je(e,de,we,J,ce,8),je(e,J,ce,de,we,16),je(e,de,we,J,ce,24)}function je(e,n,t,s,o,r){ve.fill(0);for(let i=0;i<e;i++)ve[t[i]>>>r&255]++;let u=0;for(let i=0;i<256;i++){let l=ve[i];ve[i]=u,u+=l}for(let i=0;i<e;i++){let l=t[i],c=ve[l>>>r&255]++;s[c]=n[i],o[c]=l}}function $e(e,n,t,s,o){return e=(e-t)*o|0,n=(n-s)*o|0,e=(e|e<<8)&16711935,e=(e|e<<4)&252645135,e=(e|e<<2)&858993459,e=(e|e<<1)&1431655765,n=(n|n<<8)&16711935,n=(n|n<<4)&252645135,n=(n|n<<2)&858993459,n=(n|n<<1)&1431655765,e|n<<1}function Hn(e){let n=e,t=e;do(n.x<t.x||n.x===t.x&&n.y<t.y)&&(t=n),n=n.next;while(n!==e);return t}function Ne(e,n,t,s,o,r,u,i){return(o-u)*(n-i)>=(e-u)*(r-i)&&(e-u)*(s-i)>=(t-u)*(n-i)&&(t-u)*(r-i)>=(o-u)*(s-i)}function Nn(e,n){let t=Ae(e,n)&&P(e.prev,e,e.next)>0&&P(n.prev,n,n.next)>0;return e.next.i!==n.i&&(t||Ie(e,n)&&Ie(n,e)&&(P(e.prev,e,n.prev)!==0||P(e,n.prev,n)!==0))&&!Vn(e,n)&&(t||Yn(e,n))}function P(e,n,t){return(n.y-e.y)*(t.x-n.x)-(n.x-e.x)*(t.y-n.y)}function Ae(e,n){return e.x===n.x&&e.y===n.y}function yn(e,n,t,s,o=!0){let r=P(e,n,t),u=P(e,n,s),i=P(t,s,e),l=P(t,s,n);return(r>0&&u<0||r<0&&u>0)&&(i>0&&l<0||i<0&&l>0)?!0:o?!!(r===0&&Ee(e,t,n)||u===0&&Ee(e,s,n)||i===0&&Ee(t,e,s)||l===0&&Ee(t,n,s)):!1}function Ee(e,n,t){return n.x<=Math.max(e.x,t.x)&&n.x>=Math.min(e.x,t.x)&&n.y<=Math.max(e.y,t.y)&&n.y>=Math.min(e.y,t.y)}function Vn(e,n){let t=Math.min(e.x,n.x),s=Math.max(e.x,n.x),o=Math.min(e.y,n.y),r=Math.max(e.y,n.y),u=e;do{let i=u.next;if(u.x>s&&i.x>s||u.x<t&&i.x<t||u.y>r&&i.y>r||u.y<o&&i.y<o){u=i;continue}if(u.i!==e.i&&i.i!==e.i&&u.i!==n.i&&i.i!==n.i&&yn(u,i,e,n))return!0;u=i}while(u!==e);return!1}function Ie(e,n){return P(e.prev,e,e.next)<0?P(e,n,e.next)>=0&&P(e,e.prev,n)>=0:P(e,n,e.prev)<0||P(e,e.next,n)<0}function Yn(e,n){let t=e,s=!1,o=(e.x+n.x)/2,r=(e.y+n.y)/2;do{let u=t.next;t.y>r!=u.y>r&&o<(u.x-t.x)*(r-t.y)/(u.y-t.y)+t.x&&(s=!s),t=u}while(t!==e);return s}function pn(e,n){let t=qe(e.i,e.x,e.y),s=qe(n.i,n.x,n.y),o=e.next,r=n.prev;return e.next=n,n.prev=e,t.next=o,o.prev=t,s.next=t,t.prev=s,r.next=s,s.prev=r,s}function mn(e,n,t,s){let o=qe(e,n,t);return s?(o.next=s.next,o.prev=s,s.next.prev=o,s.next=o):(o.prev=o,o.next=o),o}function Te(e){e.next.prev=e.prev,e.prev.next=e.next,e.prevZ&&(e.prevZ.nextZ=e.nextZ),e.nextZ&&(e.nextZ.prevZ=e.prevZ),Ke&&Cn(e.prev,e.next)}function qe(e,n,t){return{i:e,x:n,y:t,prev:null,next:null,z:0,prevZ:null,nextZ:null}}function Xn(e,n,t,s){let o=0;for(let r=n,u=t-s;r<t;r+=s)o+=(e[u]-e[r])*(e[r+1]+e[u+1]),u=r;return o}var Gn=.7,Dn=Math.tan(Math.PI/6),Wn=Math.tan(35*Math.PI/180),Kn=Math.tan(Math.PI/18),ke=.65,Be=.4;function vn(e,n,t,s,o,r,u){let i=String(t.roof_shape??"");if(!i||i==="flat"||i==="sawtooth"||et(e,u))return;let l=$n(e);if(!l||Math.abs(en(e))<Gn*4*l.halfU*l.halfV)return;let c=l.u,a=l.v,f=l.halfU*r,h=l.halfV*r;f<h&&([c,a,f,h]=[a,c,h,f]);let m=wn(t.roof_direction),b=m===void 0?void 0:[Math.sin(m*Math.PI/180),-Math.cos(m*Math.PI/180)];(b!==void 0?Math.abs(b[0]*c[0]+b[1]*c[1])>Math.abs(b[0]*a[0]+b[1]*a[1]):t.roof_orientation==="across")&&i!=="skillion"&&([c,a,f,h]=[a,c,h,f]);let x=Qn(i,f,h,b&&[Mn(b,c),Mn(b,a)]);if(!x)return;let U=Math.max(0,o-s),k=wn(t.roof_height),M,y;if(k!==void 0&&k>0)y=o,M=Math.max(s+Math.min(2,U/2),o-k);else{let p=Math.min(Jn(i,h),U*.6,30);t.height_source==="height"?(y=o,M=o-p):(M=o,y=o+p)}let I=y-M;if(!(I>=.3))return;let[R,V]=l.centre,B=(p,A)=>{let w=(p-R)*r,g=(A-V)*r;return[w*c[0]+g*c[1],w*a[0]+g*a[1]]},j=(p,A)=>[R+(p*c[0]+A*a[0])/r,V+(p*c[1]+A*a[1])/r],Q=(p,A)=>{let w=1/0;for(let[g,N,E]of x)w=Math.min(w,g*p+N*A+E);return Math.max(0,w)},$=(p,A)=>M+I*Q(...B(p,A)),L=[];for(let p=0;p+1<n.length;p+=2)L.push(...B(n[p],n[p+1]));en(L)<0&&nt(L);let z=1.01,q=[-f*z,-h*z,f*z,-h*z,f*z,h*z,-f*z,h*z],Y=[];for(let[p,A]of x.entries()){let w=q;for(let[F,H]of x.entries()){if(F===p)continue;let C=A[0]-H[0],X=A[1]-H[1],ne=A[2]-H[2];if(Math.abs(C)+Math.abs(X)+Math.abs(ne)<1e-9){F<p&&(w=[]);continue}if(w=gn(w,C,X,ne),w.length<6)break}if(w.length<6)continue;let g=L,N=w.length/2;for(let F=0;F<N&&g.length>=6;F+=1){let[H,C]=[w[F*2],w[F*2+1]],[X,ne]=[w[(F+1)%N*2],w[(F+1)%N*2+1]];g=gn(g,ne-C,H-X,-(ne-C)*H-(H-X)*C)}if(g.length<6||Math.abs(en(g))<1e-6)continue;let E=[];for(let F=0;F<g.length;F+=2)E.push(...j(g[F],g[F+1]));let se=Ue(E);if(se.length===0)continue;let ee=I*A[0],oe=I*A[1],T=ee*c[0]+oe*a[0],v=ee*c[1]+oe*a[1],O=Math.hypot(T,v,1);Y.push({ring:E,triangles:se,normal:[-T/O,-v/O]})}if(Y.length!==0)return{eaves:M,ridge:y,faces:Y,heightAt:$,breaks(p,A,w,g){let[N,E]=B(p,A),[se,ee]=B(w,g),oe=[];for(let T=0;T<x.length;T+=1)for(let v=T+1;v<x.length;v+=1){let O=(he,S)=>(x[T][0]-x[v][0])*he+(x[T][1]-x[v][1])*S+(x[T][2]-x[v][2]),F=O(N,E),H=O(se,ee);if(F===H||F*H>0)continue;let C=F/(F-H);if(C<=1e-4||C>=1-1e-4)continue;let X=N+(se-N)*C,ne=E+(ee-E)*C;x[T][0]*X+x[T][1]*ne+x[T][2]<=Q(X,ne)+1e-6&&oe.push(C)}return[...new Set(oe.map(T=>Math.round(T*1e6)/1e6))].sort((T,v)=>T-v)}}}function Jn(e,n){switch(e){case"skillion":return 2*n*Kn;case"pyramidal":return n*Wn;case"gambrel":case"mansard":return n*.8;case"dome":case"onion":case"round":return n;default:return n*Dn}}function Qn(e,n,t,s){let o=n,r=t,u=[[0,-1/r,1],[0,1/r,1]],i=c=>c.flatMap(a=>[1,-1].flatMap(f=>{let h=a==="v"?r:o,m=ke/(Be*r)*f,b=(1-ke)/((1-Be)*r)*f,d=x=>a==="v"?[0,-x]:[-x,0];return[[...d(m),ke/(Be*r)*h],[...d(b),ke+(1-ke)/((1-Be)*r)*(h-Be*r)]]})),l=[.99,.8,.5,.2];switch(e){case"gabled":return u;case"saltbox":{let c=r/3;return[[0,-1/(r-c),1+c/(r-c)],[0,1/(r+c),1-c/(r+c)]]}case"hipped":return[...u,[-1/r,0,o/r],[1/r,0,o/r]];case"half_hipped":return[...u,[-1/r,0,.5+o/r],[1/r,0,.5+o/r]];case"pyramidal":return[...u,[-1/o,0,1],[1/o,0,1]];case"gambrel":return i(["v"]);case"mansard":return i(["v","u"]);case"skillion":{let[c,a]=s??[0,1],f=Math.abs(c)*o+Math.abs(a)*r;return f>0?[[-c/(2*f),-a/(2*f),.5]]:void 0}case"round":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return[1,-1].map(f=>[0,-f*c/(r*a),1/a])})];case"dome":case"onion":return[[0,0,1],...l.flatMap(c=>{let a=Math.sqrt(1-c*c);return Array.from({length:12},(f,h)=>{let m=h*Math.PI/6;return[-c*Math.cos(m)/(o*a),-c*Math.sin(m)/(r*a),1/a]})})];default:return}}function gn(e,n,t,s){let o=e.length/2,r=[];for(let u=0;u<o;u+=1){let i=e[u*2],l=e[u*2+1],c=e[(u+1)%o*2],a=e[(u+1)%o*2+1],f=n*i+t*l+s,h=n*c+t*a+s;if(f<=0&&r.push(i,l),f<0&&h>0||f>0&&h<0){let m=f/(f-h);r.push(i+(c-i)*m,l+(a-l)*m)}}return r}function $n(e){let n=qn(e);if(n.length<3)return;let t,s=1/0;for(let o=0;o<n.length;o+=1){let[r,u]=n[o],[i,l]=n[(o+1)%n.length],c=Math.hypot(i-r,l-u);if(c===0)continue;let a=[(i-r)/c,(l-u)/c],f=[-a[1],a[0]],[h,m,b,d]=[1/0,-1/0,1/0,-1/0];for(let[U,k]of n){let M=U*a[0]+k*a[1],y=U*f[0]+k*f[1];h=Math.min(h,M),m=Math.max(m,M),b=Math.min(b,y),d=Math.max(d,y)}let x=(m-h)*(d-b);if(x<s){s=x;let U=(h+m)/2,k=(b+d)/2;t={centre:[U*a[0]+k*f[0],U*a[1]+k*f[1]],u:a,v:f,halfU:(m-h)/2,halfV:(d-b)/2}}}return t&&t.halfU>0&&t.halfV>0?t:void 0}function qn(e){let n=[];for(let r=0;r+1<e.length;r+=2)n.push([e[r],e[r+1]]);n.sort((r,u)=>r[0]-u[0]||r[1]-u[1]);let t=(r,u,i)=>(u[0]-r[0])*(i[1]-r[1])-(u[1]-r[1])*(i[0]-r[0]),s=[];for(let r of n){for(;s.length>=2&&t(s.at(-2),s.at(-1),r)<=0;)s.pop();s.push(r)}let o=[];for(let r of n.reverse()){for(;o.length>=2&&t(o.at(-2),o.at(-1),r)<=0;)o.pop();o.push(r)}return[...s.slice(0,-1),...o.slice(0,-1)]}function et(e,n){let[t,s,o,r]=[1/0,-1/0,1/0,-1/0];for(let i=0;i+1<e.length;i+=2)t=Math.min(t,e[i]),s=Math.max(s,e[i]),o=Math.min(o,e[i+1]),r=Math.max(r,e[i+1]);let u=e.length/2;for(let i=0;i<u;i+=1){let l=e[i*2],c=e[i*2+1],a=e[(i+1)%u*2],f=e[(i+1)%u*2+1];if(t<0&&l===t&&a===t||s>n&&l===s&&a===s||o<0&&c===o&&f===o||r>n&&c===r&&f===r)return!0}return!1}function en(e){let n=0,t=e.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;n+=e[s*2]*e[o*2+1]-e[o*2]*e[s*2+1]}return n/2}function nt(e){let n=[];for(let t=0;t<e.length;t+=2)n.push([e[t],e[t+1]]);n.reverse(),e.splice(0,e.length,...n.flat())}function Mn(e,n){return e[0]*n[0]+e[1]*n[1]}function wn(e){let n=typeof e=="string"?Number(e):e;return typeof n=="number"&&Number.isFinite(n)?n:void 0}var Mt=Object.freeze({surface:0,secondary:1,glass:2,accent:3,roof:4}),wt=Object.freeze({unclassified:0,wall:1,door:2,roof:3,window:4,lamp:5,logo:6}),vt=Object.freeze([1,1,4,1,3]),Rt=Object.freeze({levels:3,vertices:2e5,triangles:2e5,footprintPoints:4096,radius:2e3,height:1e3,models:512,bytes:16*1024*1024});function nn(e,n,t){for(let s of e){let o=!1;for(let r=0,u=s.length-2;r<s.length;u=r,r+=2){let i=s[r],l=s[r+1],c=s[u],a=s[u+1];l>t!=a>t&&n<(c-i)*(t-l)/(a-l)+i&&(o=!o)}if(o)return!0}return!1}function In(e,n,t){for(let s of n){let[o,r,u,i]=An(s);for(let l of e){let[c,a,f,h]=An(l);if(!(c>u-t||f<o+t||a>i-t||h<r+t)&&(Rn(l,s,t)||Rn(s,l,t)))return!0}}return!1}function Rn(e,n,t){let s=(o,r)=>nn([n],o,r)&&ot(n,o,r)>t;for(let o=0;o+1<e.length;o+=2)if(s(e[o],e[o+1]))return!0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[u,i,l,c]=[e[r],e[r+1],e[o],e[o+1]],a=[0,1];for(let f=0,h=n.length-2;f<n.length;h=f,f+=2){let m=tt(u,i,l,c,n[h],n[h+1],n[f],n[f+1]);m!==void 0&&a.push(m)}if(a.length!==2){a.sort((f,h)=>f-h);for(let f=1;f<a.length;f+=1){let h=(a[f-1]+a[f])/2;if(s(u+(l-u)*h,i+(c-i)*h))return!0}}}return!1}function tt(e,n,t,s,o,r,u,i){let l=t-e,c=s-n,a=u-o,f=i-r,h=l*f-c*a;if(h===0)return;let m=((o-e)*f-(r-n)*a)/h,b=((o-e)*c-(r-n)*l)/h;return m>0&&m<1&&b>=0&&b<=1?m:void 0}function ot(e,n,t){let s=1/0;for(let o=0,r=e.length-2;o<e.length;r=o,o+=2){let[u,i,l,c]=[e[r],e[r+1],e[o],e[o+1]],a=(l-u)**2+(c-i)**2,f=a>0?Math.max(0,Math.min(1,((n-u)*(l-u)+(t-i)*(c-i))/a)):0;s=Math.min(s,Math.hypot(n-(u+(l-u)*f),t-(i+(c-i)*f)))}return s}function An(e){let[n,t,s,o]=[1/0,1/0,-1/0,-1/0];for(let r=0;r+1<e.length;r+=2)n=Math.min(n,e[r]),s=Math.max(s,e[r]),t=Math.min(t,e[r+1]),o=Math.max(o,e[r+1]);return[n,t,s,o]}var te=8192,tn=28,rt={commercial:1,active:2,destination:3},st={residential:1,...Object.fromEntries(["civic","commercial","education","entertainment","medical","transportation"].map(e=>[e,2])),...Object.fromEntries(["agricultural","industrial","military","outbuilding","religious","service"].map(e=>[e,3]))},it={dining:1,entertainment:2,nightlife:3},on={...Object.fromEntries(["alcoholic_beverage_venue","bar","beer","biergarten","brewery","casino","cocktail_bar","dance_club","karaoke","lounge","music_venue","night_club","nightclub","pub","wine_bar"].map(e=>[e,3])),...Object.fromEntries(["arts_centre","bowling_alley","cinema","comedy_club","concert_hall","movie_theater","opera","performing_arts_venue","theater","theatre","theatre_venue"].map(e=>[e,2])),...Object.fromEntries(["casual_eatery","fast_food","fast_food_restaurant","food_court","restaurant"].map(e=>[e,1]))},ut=6,lt=.5,ct=.5;function Un(e,n){let t=te/e.extent,s=n.tileMetres/te,o=n.defaultHeight??5,r=Number.isFinite(n.edgeRadius)?Math.max(0,Math.min(1,n.edgeRadius))/s:0,u=new sn,i=new Ve,l=new Ve,c=[],a=[],f=0,h=mt(e,n);for(let[y,I]of e.features.entries()){if(I.type!=="Polygon"||c.length>=65535)continue;let R=I.properties,V=R.hide_3d;if(V===!0||V===1||V==="1"||V==="true")continue;let B=Math.min(6e3,Tn(R.render_height)??o),j=Math.min(B,Tn(R.render_min_height)??0),Q=rt[String(R.building_tone??"")]??0,$=st[String(R.class??"")]??0,L=h.get(y)??0,z=c.length,q=!1;for(let Y of bt(I.geometry,t)){let p=Y.map(dt).filter(v=>v.length>=6);if(p.length===0||Ye(p[0])===0)continue;let[A,w]=xt(p[0]);if(n.masks?.length&&(nn(n.masks,A,w)||In(p,n.masks,ct/s)))continue;let g={cell:Math.min(7,Math.max(0,Math.floor(w/1024)))*8+Math.min(7,Math.max(0,Math.floor(A/1024))),bounds:[1/0,1/0,-1/0,-1/0,B+.1],roofsFrom:i.length,roofsCount:0,wallsFrom:l.length,wallsCount:0};for(let v of p)for(let O=0;O<v.length;O+=2)g.bounds[0]=Math.min(g.bounds[0],v[O]-1),g.bounds[1]=Math.min(g.bounds[1],v[O+1]-1),g.bounds[2]=Math.max(g.bounds[2],v[O]+1),g.bounds[3]=Math.max(g.bounds[3],v[O+1]+1);q=!0,f=Math.max(f,B);let N=Number.isSafeInteger(I.id),E=rn(N?I.id*.0731:A*.0731+w*.19137+z*1.618),se=!N&&p[0].some(v=>v<=0||v>=te)?rn(B*7.31+j*3.17+Q):E,ee={base:j,top:B,jitter:se,seed:E,tone:Q,group:$,use:L,cx:A,cy:w,index:z},oe=p.map((v,O)=>ht(v,O,r,n.cornerAngle));p=oe.map(({ring:v})=>v);let T=Y.length===1&&p.length===1?vn(Y[0],p[0],R,j,B,s,te):void 0;T?(f=Math.max(f,T.ridge),g.bounds[4]=Math.max(g.bounds[4],T.ridge+.1),ft(T,ee,u,i)):at(p,ee,u,i);for(let[v,O]of p.entries()){let F=Ye(O)>0,H=v===0?F?-1:1:F?1:-1,C=O.length/2,X=S=>{let Z=(S%C+C)%C;return[O[Z*2],O[Z*2+1]]},ne=S=>{let[Z,W]=X(S-1),[G,D]=X(S),[ie,me]=X(S+1);if(fe(Z,W,G,D)||fe(G,D,ie,me))return!1;let be=Math.hypot(G-Z,D-W),ue=Math.hypot(ie-G,me-D);if(!be||!ue)return!1;let Fe=-(D-W)/be*H,_e=(G-Z)/be*H;return(Fe*(ie-G)+_e*(me-D))/ue>lt},Xe=(S,Z)=>T?T.heightAt(S,Z):B,he=0;for(let S=0;S<C;S+=1){let[Z,W]=X(S),[G,D]=X(S+1),ie=Math.hypot(G-Z,D-W);if(ie===0||fe(Z,W,G,D))continue;let me=-(D-W)/ie*H,be=(G-Z)/ie*H,ue=oe[v].normals[S]??[me,be],Fe=oe[v].normals[(S+1)%C]??[me,be],_e=K=>{let pe=ue[0]+(Fe[0]-ue[0])*K,ge=ue[1]+(Fe[1]-ue[1])*K,le=Math.hypot(pe,ge)||1;return[pe/le,ge/le]},re=[0,...T?T.breaks(Z,W,G,D):[],1];for(let K=0;K+1<re.length;K+=1){let pe=K===0,ge=K+2===re.length,le=Z+(G-Z)*re[K],Pe=W+(D-W)*re[K],Oe=Z+(G-Z)*re[K+1],Le=W+(D-W)*re[K+1],Me=Math.hypot(Oe-le,Le-Pe)/te*n.tileMetres,ze=(pe&&ne(S)?1:0)|(ge&&ne(S+1)?2:0)|(v>0?4:0),Ce=(pe&&un(Z,W)?1:0)|(ge&&un(G,D)?2:0),Ge=he+Me,Se=_e(re[K]),Ze=_e(re[K+1]),cn=Xe(le,Pe),an=Xe(Oe,Le),xe=u.write(ee,le,Pe,0,Se[0],Se[1],1,he,ze,Me,0,Ce,cn);u.write(ee,Oe,Le,0,Ze[0],Ze[1],1,Ge,ze,Me,1,Ce,an),u.write(ee,le,Pe,1,Se[0],Se[1],1,he,ze,Me,0,Ce,cn),u.write(ee,Oe,Le,1,Ze[0],Ze[1],1,Ge,ze,Me,1,Ce,an),l.push(xe,xe+1,xe+2,xe+1,xe+3,xe+2),he=Ge}}}g.roofsCount=i.length-g.roofsFrom,g.wallsCount=l.length-g.wallsFrom,a.push(g)}q&&c.push(R)}if(u.count===0)return;let m=new Uint32Array(i.length+l.length),b=[],d=0,x=i.length,U=i.values(),k=l.values();a.sort((y,I)=>y.cell-I.cell);let M=-1;for(let y of a){y.cell!==M&&(M=y.cell,b.push({bounds:[...y.bounds],roofsFrom:d,roofsCount:0,wallsFrom:x,wallsCount:0}));let I=b.at(-1);for(let R=0;R<5;R+=1)I.bounds[R]=R<2?Math.min(I.bounds[R],y.bounds[R]):Math.max(I.bounds[R],y.bounds[R]);m.set(U.subarray(y.roofsFrom,y.roofsFrom+y.roofsCount),d),m.set(k.subarray(y.wallsFrom,y.wallsFrom+y.wallsCount),x),d+=y.roofsCount,x+=y.wallsCount,I.roofsCount+=y.roofsCount,I.wallsCount+=y.wallsCount}return{groups:b,vertices:u.bytes(),indices:m,wallsFrom:i.length,top:f,features:c}}function at(e,n,t,s){let o=[],r=[];for(let l of e)r.push(o.length/2),o.push(...l);r.push(o.length/2);let u=(l,c)=>{let a=r.findIndex((d,x)=>l>=d&&l<r[x+1]),[f,h]=[r[a],r[a+1]-1];if(c<f||c>h)return!1;let[m,b]=l<c?[l,c]:[c,l];return b-m!==1&&!(m===f&&b===h)?!1:!fe(o[l*2],o[l*2+1],o[c*2],o[c*2+1])},i=Ue(o,r.slice(1,-1),2);for(let l=0;l+2<i.length;l+=3){let c=[i[l],i[l+1],i[l+2]],a=0;for(let f=0;f<3;f+=1)u(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries())s.push(t.write(n,o[h*2],o[h*2+1],1,0,0,0,0,0,0,f,a))}}function ft(e,n,t,s){for(let o of e.faces){let r=o.ring,u=r.length/2,i=(l,c)=>{let[a,f]=l<c?[l,c]:[c,l];return f-a!==1&&!(a===0&&f===u-1)?!1:!fe(r[l*2],r[l*2+1],r[c*2],r[c*2+1])};for(let l=0;l+2<o.triangles.length;l+=3){let c=[o.triangles[l],o.triangles[l+1],o.triangles[l+2]],a=0;for(let f=0;f<3;f+=1)i(c[(f+1)%3],c[(f+2)%3])&&(a|=1<<f);for(let[f,h]of c.entries()){let m=r[h*2],b=r[h*2+1];s.push(t.write(n,m,b,1,o.normal[0],o.normal[1],0,0,0,0,f,a,e.heightAt(m,b)))}}}}function ht(e,n,t,s=Math.PI/6){if(t<1)return{ring:e,normals:[]};let o=e.length/2,r=Ye(e)>0,u=n===0?r?-1:1:r?1:-1,i=[],l=[],c=(a,f,h)=>{a=ye(a),f=ye(f),!(i.at(-2)===a&&i.at(-1)===f)&&(i.push(a,f),l.push(h))};for(let a=0;a<o;a+=1){let f=(a+o-1)%o,h=(a+1)%o,[m,b]=[e[f*2],e[f*2+1]],[d,x]=[e[a*2],e[a*2+1]],[U,k]=[e[h*2],e[h*2+1]],M=Math.hypot(d-m,x-b),y=Math.hypot(U-d,k-x);if(!M||!y||un(d,x)||fe(m,b,d,x)||fe(d,x,U,k)){c(d,x);continue}let[I,R]=[(d-m)/M,(x-b)/M],[V,B]=[(U-d)/y,(k-x)/y],j=Math.atan2(I*B-R*V,I*V+R*B);if(j*u>=-.08||Math.abs(j)>Math.PI*.85){c(d,x);continue}let Q=Math.tan(Math.abs(j)/2),$=Math.min(t*Q,M*.2,y*.2),L=$/Q;if(L<1){c(d,x);continue}let[z,q]=[-R*u,I*u],[Y,p]=[d-I*$-z*L,x-R*$-q*L],A=Math.atan2(q,z),w=Math.max(s<=Math.PI/6?2:1,Math.ceil(Math.abs(j)/Math.max(Math.PI/6,Math.min(Math.PI/3,s))));for(let g=0;g<=w;g+=1){let N=A+j*g/w,E=[Math.cos(N),Math.sin(N)];c(Y+E[0]*L,p+E[1]*L,E)}}return i[0]===i.at(-2)&&i[1]===i.at(-1)&&(i.splice(-2),l.pop()),i.length>=6?{ring:i,normals:l}:{ring:e,normals:[]}}var sn=class{#e=new ArrayBuffer(4096*tn);#n=new DataView(this.#e);count=0;write(n,t,s,o,r,u,i,l,c,a,f=0,h=0,m=n.top){let b=tn;if((this.count+1)*b>this.#e.byteLength){let U=new ArrayBuffer(this.#e.byteLength*2);new Uint8Array(U).set(new Uint8Array(this.#e)),this.#e=U,this.#n=new DataView(U)}let d=this.#n,x=this.count*b;return d.setUint16(x,ye(t),!0),d.setUint16(x+2,ye(s),!0),d.setUint16(x+4,Math.min(65535,Math.round(n.base*10)),!0),d.setUint16(x+6,Math.min(65535,Math.max(0,Math.round(m*10))),!0),d.setInt8(x+8,Math.round(r*127)),d.setInt8(x+9,Math.round(u*127)),d.setUint8(x+10,o),d.setUint8(x+11,i),d.setUint16(x+12,Math.round(l*10)%65536,!0),d.setUint8(x+14,Math.round(n.jitter*255)),d.setUint8(x+15,Math.round(n.seed*255)),d.setUint16(x+16,ye(n.cx),!0),d.setUint16(x+18,ye(n.cy),!0),d.setUint8(x+20,n.tone+4*n.group),d.setUint8(x+21,n.use),d.setUint8(x+22,c),d.setUint8(x+23,Math.min(255,Math.round(a))),d.setUint16(x+24,n.index,!0),d.setUint8(x+26,f),d.setUint8(x+27,h),this.count+=1,this.count-1}bytes(){return new Uint8Array(this.#e.slice(0,this.count*tn))}},Ve=class{#e=new Uint32Array(8192);length=0;push(...n){if(this.length+n.length>this.#e.length){let t=new Uint32Array(Math.max(this.length+n.length,this.#e.length*2));t.set(this.#e),this.#e=t}for(let t of n)this.#e[this.length++]=t}values(){return this.#e.subarray(0,this.length)}};function mt(e,n){let t=new Map;if(e.features.some(i=>i.properties.night_use!==void 0)){for(let[i,l]of e.features.entries()){let c=it[String(l.properties.night_use??"")];c&&t.set(i,c)}return t}let s=n.places;if(!s||s.features.length===0)return t;let o=ut/n.tileMetres*e.extent,r=[];for(let[i,l]of e.features.entries()){if(l.type!=="Polygon")continue;let c=l.properties.hide_3d;if(c===!0||c===1||c==="1"||c==="true")continue;let a=[1/0,1/0,-1/0,-1/0];for(let f of l.geometry)for(let h=0;h+1<f.length;h+=2)a[0]=Math.min(a[0],f[h]),a[1]=Math.min(a[1],f[h+1]),a[2]=Math.max(a[2],f[h]),a[3]=Math.max(a[3],f[h+1]);r.push({index:i,rings:l.geometry,box:a})}let u=e.extent/s.extent;for(let i of s.features){if(i.type!=="Point")continue;let l=i.properties,c=on[String(l.type??"")]??on[String(l.subclass??"")]??on[String(l.class??"")];if(c)for(let a of i.geometry)for(let f=0;f+1<a.length;f+=2){let h=a[f]*u,m=a[f+1]*u,b,d,x=o;for(let k of r){let[M,y,I,R]=k.box;if(h<M-o||h>I+o||m<y-o||m>R+o)continue;let V=!1;for(let B of k.rings)for(let j=0,Q=B.length-2;j+1<B.length;Q=j,j+=2){let $=B[Q],L=B[Q+1],z=B[j],q=B[j+1];q>m!=L>m&&h<($-z)*(m-q)/(L-q)+z&&(V=!V);let Y=z-$,p=q-L,A=Y*Y+p*p,w=A?Math.min(1,Math.max(0,((h-$)*Y+(m-L)*p)/A)):0,g=Math.hypot(h-$-w*Y,m-L-w*p);g<x&&(x=g,d=k.index)}if(V){b=k.index;break}}let U=b??d;U!==void 0&&t.set(U,Math.max(t.get(U)??0,c))}}return t}function ye(e){return Math.min(te,Math.max(0,Math.round(e)))}function Tn(e){let n=Number(e);return Number.isFinite(n)&&n>0?n:void 0}function bt(e,n){let t=[],s;for(let o of e){let r=[];for(let i=0;i+1<o.length;i+=2){let l=o[i]*n,c=o[i+1]*n;r.length&&l===r.at(-2)&&c===r.at(-1)||r.push(l,c)}if(r.length>=6&&r[0]===r.at(-2)&&r[1]===r.at(-1)&&(r.length-=2),r.length<6)continue;let u=Math.sign(Ye(r));u&&(s??=u,u===s||t.length===0?t.push([r]):t.at(-1).push(r))}return t}function Ye(e){let n=0,t=e.length/2;for(let s=0;s<t;s+=1){let o=(s+1)%t;n+=e[s*2]*e[o*2+1]-e[o*2]*e[s*2+1]}return n}function xt(e){let n=0,t=0,s=e.length/2;for(let o=0;o<s;o+=1)n+=e[o*2],t+=e[o*2+1];return[n/s,t/s]}function un(e,n){return e<=0||n<=0||e>=te||n>=te}function fe(e,n,t,s){let o=te;return e<=0&&t<=0||e>=o&&t>=o||n<=0&&s<=0||n>=o&&s>=o}function dt(e){let n=te,t=[...e],s=[[0,0,o=>o>=0],[0,n,o=>o<=n],[1,0,o=>o>=0],[1,n,o=>o<=n]];for(let[o,r,u]of s){let i=[],l=t.length/2;for(let c=0;c<l;c+=1){let a=[t[c*2],t[c*2+1]],f=[t[(c+1)%l*2],t[(c+1)%l*2+1]],h=u(a[o]),m=u(f[o]);if(h&&i.push(a[0],a[1]),h!==m){let b=(r-a[o])/(f[o]-a[o]);i.push(a[0]+(f[0]-a[0])*b,a[1]+(f[1]-a[1])*b)}}if(t=i,t.length<6)return[]}return t}function rn(e){let n=Math.sin(e*12.9898+78.233)*43758.5453;return n-Math.floor(n)}var ln=globalThis;ln.onmessage=({data:e})=>{try{let n=Un(e.layer,e.options);ln.postMessage({mesh:n},n?[n.vertices.buffer,n.indices.buffer]:[])}catch(n){ln.postMessage({error:String(n)})}};})();\n'], { type: "text/javascript" })
6582
6687
  );
6583
6688
  try {
6584
6689
  return new Worker(url, { name: "tileflow-buildings" });
@@ -7075,6 +7180,9 @@ uniform float u_corner;
7075
7180
  uniform float u_night; // 0 by day, 1 at night
7076
7181
  uniform float u_windows; // whether to draw night windows
7077
7182
  uniform float u_flood; // flood light strength at night
7183
+ uniform float u_architectural_intensity;
7184
+ uniform vec3 u_architectural_colour;
7185
+ uniform vec2 u_architectural_wall; // minimum height, wall radius / building height
7078
7186
  uniform float u_edge; // how strongly roof outlines and outer corners catch the light, 0 for none
7079
7187
  uniform float u_edge_width; // their width, in the drawing buffer's pixels
7080
7188
  uniform vec3 u_edge_colour; // the application's colour for them
@@ -7227,6 +7335,11 @@ void main() {
7227
7335
  }
7228
7336
  // Flood lights wash the foot of lit buildings at night.
7229
7337
  colour += floodOf(v_building.y, top, seed) * u_flood * 0.3 * (1.0 - smoothstep(0.0, 6.0, up));
7338
+ if (top > u_architectural_wall.x && u_architectural_intensity > 0.0) {
7339
+ float radius = top * u_architectural_wall.y;
7340
+ float share = u_architectural_intensity * max(0.0, 1.0 - up / max(radius, 0.001));
7341
+ colour = mix(colour, u_architectural_colour, share);
7342
+ }
7230
7343
  // The places busy at night light their ground floor's windows: panes of about 3.6 m, which far
7231
7344
  // away give way to their average so nothing shimmers.
7232
7345
  if (v_building.w > 0.5 && v_wall.z > 3.0 && v_wall.y < 4.0 && u_flood > 0.0) {
@@ -7302,6 +7415,40 @@ void main() {
7302
7415
  float close = near * near * near * near * near * near;
7303
7416
  fragColor = vec4((v_flood * 0.35 * near * near + v_shop * 0.12 * close) * u_flood * v_strength, 0.0);
7304
7417
  }`;
7418
+ var buildingArchitecturalFloodVertex = (projected) => `#version 300 es
7419
+ ${projected}
7420
+ ${attributes}
7421
+ ${growth}
7422
+ uniform vec2 u_radius;
7423
+ uniform float u_min_height;
7424
+ out float v_out;
7425
+ void main() {
7426
+ if (a_height.y * 0.1 < u_min_height || a_height.x > 5.0 || progressOf() <= 0.0) {
7427
+ gl_Position = vec4(2.0, 2.0, 2.0, 1.0);
7428
+ return;
7429
+ }
7430
+ float radius = mix(u_radius.x, u_radius.y, a_wave.x);
7431
+ gl_Position = projectTileFor3D(a_pos + a_normal * (radius / u_metres) * a_flags.x, 0.0);
7432
+ v_out = a_flags.x;
7433
+ }`;
7434
+ var buildingArchitecturalFloodFragment = `#version 300 es
7435
+ precision highp float;
7436
+ in float v_out;
7437
+ uniform float u_intensity;
7438
+ out vec4 fragColor;
7439
+ void main() {
7440
+ float near = 1.0 - v_out;
7441
+ fragColor = vec4(vec3(1.0 - u_intensity * pow(near, 2.101)), 1.0);
7442
+ }`;
7443
+ var buildingArchitecturalFloodComposite = `#version 300 es
7444
+ precision highp float;
7445
+ uniform sampler2D u_mask;
7446
+ uniform vec3 u_colour;
7447
+ out vec4 fragColor;
7448
+ void main() {
7449
+ float light = 1.0 - texelFetch(u_mask, ivec2(gl_FragCoord.xy), 0).r;
7450
+ fragColor = vec4(u_colour, light);
7451
+ }`;
7305
7452
 
7306
7453
  // src/buildings-3d-view.ts
7307
7454
  function buildingBoxVisible(matrix, bounds) {
@@ -8781,6 +8928,7 @@ function createCameraTiles(options) {
8781
8928
  const tiles = /* @__PURE__ */ new Map();
8782
8929
  const queue = [];
8783
8930
  let wanted = [];
8931
+ let covered = [];
8784
8932
  let reader;
8785
8933
  let zoom = options.zoom;
8786
8934
  let origin = [0, 0];
@@ -8806,7 +8954,6 @@ function createCameraTiles(options) {
8806
8954
  const currentZoom = map.getZoom();
8807
8955
  if (currentZoom < options.minzoom - 1 || !options.active()) return;
8808
8956
  const source = ensureReader();
8809
- if (!source) return;
8810
8957
  const centre = map.getCenter();
8811
8958
  const pitch = map.getPitch();
8812
8959
  const reach = Math.min(5e3, Math.max(800, 1600 * 2 ** (16 - currentZoom) * (1 + pitch / 60)));
@@ -8831,8 +8978,13 @@ function createCameraTiles(options) {
8831
8978
  candidates.sort((a, b2) => a.distance - b2.distance);
8832
8979
  const chosen = candidates.slice(0, options.maxTiles);
8833
8980
  wanted = chosen.map(({ x: x3, y: y2 }) => `${x3}/${y2}`);
8981
+ covered = chosen.map(({ x: x3, y: y2 }) => ({ x: x3, y: y2 }));
8834
8982
  origin = cameraTileAt(centre.lng, centre.lat, zoom);
8835
8983
  metres = earth * Math.cos(centre.lat * Math.PI / 180) / 2 ** zoom / extent;
8984
+ if (!source) {
8985
+ map.triggerRepaint();
8986
+ return;
8987
+ }
8836
8988
  const now = performance.now();
8837
8989
  for (const { x: x3, y: y2 } of chosen) {
8838
8990
  const key = `${x3}/${y2}`;
@@ -8955,6 +9107,7 @@ function createCameraTiles(options) {
8955
9107
  }
8956
9108
  return list;
8957
9109
  },
9110
+ coverage: () => covered,
8958
9111
  all: () => tiles.values(),
8959
9112
  get zoom() {
8960
9113
  return zoom;
@@ -9209,11 +9362,157 @@ function inspectModelTriangles(indices, position, limit = 3e3) {
9209
9362
  return { positions, indices: preview };
9210
9363
  }
9211
9364
 
9365
+ // src/models-3d-materials.ts
9366
+ var tileflowModelSurfaceLimits = Object.freeze({
9367
+ materials: 256,
9368
+ textures: 32,
9369
+ textureDimension: 4096,
9370
+ texturePixels: 16777216,
9371
+ emitters: 128
9372
+ });
9373
+ var tileflowModelTextureChannels = [
9374
+ "baseColorTexture",
9375
+ "metallicRoughnessTexture",
9376
+ "normalTexture",
9377
+ "occlusionTexture",
9378
+ "emissiveTexture"
9379
+ ];
9380
+ function checkTileflowModelSurface(surface, vertices, indices) {
9381
+ const fail = (reason) => {
9382
+ throw new RangeError(`Invalid model surface: ${reason}.`);
9383
+ };
9384
+ if (!surface || typeof surface !== "object") fail("missing data");
9385
+ const { attributes: attributes4, materials, textures, ranges, emitters } = surface;
9386
+ if (!(Array.isArray(attributes4) || attributes4 instanceof Float32Array) || attributes4.length !== vertices * 8)
9387
+ fail("each vertex needs UV0, UV1 and RGBA");
9388
+ for (let i2 = 0; i2 < attributes4.length; i2++) {
9389
+ const value = attributes4[i2];
9390
+ if (!Number.isFinite(value) || Math.abs(value) > 65536) fail("unbounded vertex attributes");
9391
+ if (i2 % 8 >= 4 && (value < 0 || value > 1)) fail("vertex RGBA must be zero to one");
9392
+ }
9393
+ if (!Array.isArray(materials) || materials.length < 1 || materials.length > tileflowModelSurfaceLimits.materials)
9394
+ fail("material count exceeds the limit");
9395
+ if (!Array.isArray(textures) || textures.length > tileflowModelSurfaceLimits.textures)
9396
+ fail("texture count exceeds the limit");
9397
+ let pixels = 0;
9398
+ for (const texture of textures) {
9399
+ if (!texture || !(texture.bytes instanceof Uint8Array) || !texture.bytes.length)
9400
+ fail("an embedded image is missing");
9401
+ if (!["image/png", "image/jpeg"].includes(texture.mimeType)) fail("use embedded PNG or JPEG");
9402
+ const dimensions = tileflowModelImageSize(texture.bytes, texture.mimeType);
9403
+ if (!dimensions || dimensions[0] !== texture.width || dimensions[1] !== texture.height)
9404
+ fail("image dimensions disagree with the image");
9405
+ if (Math.max(texture.width, texture.height) > tileflowModelSurfaceLimits.textureDimension)
9406
+ fail("image dimensions exceed the limit");
9407
+ pixels += texture.width * texture.height;
9408
+ if (pixels > tileflowModelSurfaceLimits.texturePixels)
9409
+ fail("decoded image pixels exceed the limit");
9410
+ if (texture.wrapS !== void 0 && ![33071, 33648, 10497].includes(texture.wrapS))
9411
+ fail("unknown horizontal texture wrapping");
9412
+ if (texture.wrapT !== void 0 && ![33071, 33648, 10497].includes(texture.wrapT))
9413
+ fail("unknown vertical texture wrapping");
9414
+ if (texture.minFilter !== void 0 && ![9728, 9729, 9984, 9985, 9986, 9987].includes(texture.minFilter))
9415
+ fail("unknown texture minification");
9416
+ if (texture.magFilter !== void 0 && ![9728, 9729].includes(texture.magFilter))
9417
+ fail("unknown texture magnification");
9418
+ }
9419
+ const tuple = (value, count, min, max2) => Array.isArray(value) && value.length === count && value.every((v2) => typeof v2 === "number" && Number.isFinite(v2) && v2 >= min && v2 <= max2);
9420
+ const number = (value, min, max2) => typeof value === "number" && Number.isFinite(value) && value >= min && value <= max2;
9421
+ for (const material of materials) {
9422
+ if (!material || typeof material !== "object") fail("missing material");
9423
+ if (material.baseColorFactor !== void 0 && !tuple(material.baseColorFactor, 4, 0, 1))
9424
+ fail("base colour must have four zero-to-one components");
9425
+ if (material.emissiveFactor !== void 0 && !tuple(material.emissiveFactor, 3, 0, 1))
9426
+ fail("emission must have three zero-to-one components");
9427
+ for (const key of ["metallicFactor", "roughnessFactor", "alphaCutoff"])
9428
+ if (material[key] !== void 0 && !number(material[key], 0, 1))
9429
+ fail(`${key} must be zero to one`);
9430
+ if (material.emissiveStrength !== void 0 && !number(material.emissiveStrength, 0, 1e3))
9431
+ fail("emissive strength must be zero to 1000");
9432
+ if (material.alphaMode !== void 0 && !["OPAQUE", "MASK", "BLEND"].includes(material.alphaMode))
9433
+ fail("unknown alpha mode");
9434
+ for (const key of ["doubleSided", "unlit"])
9435
+ if (material[key] !== void 0 && typeof material[key] !== "boolean")
9436
+ fail(`${key} must be boolean`);
9437
+ for (const channel of tileflowModelTextureChannels) {
9438
+ const ref = material[channel];
9439
+ if (ref === void 0) continue;
9440
+ if (!ref || typeof ref !== "object") fail("invalid texture reference");
9441
+ if (!Number.isInteger(ref.texture) || ref.texture < 0 || ref.texture >= textures.length)
9442
+ fail("material names a missing texture");
9443
+ if (ref.texCoord !== void 0 && ref.texCoord !== 0 && ref.texCoord !== 1)
9444
+ fail("texture coordinates must name UV0 or UV1");
9445
+ if (ref.offset !== void 0 && !tuple(ref.offset, 2, -65536, 65536))
9446
+ fail("invalid texture offset");
9447
+ if (ref.scale !== void 0 && !tuple(ref.scale, 2, -65536, 65536))
9448
+ fail("invalid texture scale");
9449
+ if (ref.rotation !== void 0 && !number(ref.rotation, -1e3, 1e3))
9450
+ fail("invalid texture rotation");
9451
+ if (ref.strength !== void 0 && !number(ref.strength, 0, channel === "normalTexture" ? 100 : 1))
9452
+ fail("invalid texture channel strength");
9453
+ }
9454
+ }
9455
+ let covered = 0;
9456
+ if (!Array.isArray(ranges) || !ranges.length || ranges.length > indices / 3)
9457
+ fail("missing material ranges");
9458
+ for (const range of ranges) {
9459
+ if (!range || typeof range !== "object") fail("invalid material range");
9460
+ if (range.emissiveBounds !== void 0 && (!tuple(range.emissiveBounds, 2, -2e3, 2e3) || range.emissiveBounds[0] > range.emissiveBounds[1]))
9461
+ fail("invalid material emission bounds");
9462
+ if (!Number.isInteger(range.from) || range.from !== covered || !Number.isInteger(range.count) || range.count <= 0 || range.count % 3 !== 0 || !Number.isInteger(range.material) || range.material < 0 || range.material >= materials.length)
9463
+ fail("material ranges must cover whole triangles in order");
9464
+ covered += range.count;
9465
+ if (covered > indices) fail("material range exceeds the mesh");
9466
+ }
9467
+ if (covered !== indices) fail("material ranges must cover the mesh");
9468
+ if (emitters !== void 0) {
9469
+ if (!Array.isArray(emitters) || emitters.length > tileflowModelSurfaceLimits.emitters)
9470
+ fail("emitter count exceeds the limit");
9471
+ for (const emitter of emitters) {
9472
+ if (!emitter || typeof emitter !== "object") fail("invalid area emitter");
9473
+ 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))
9474
+ fail("invalid area emitter");
9475
+ 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)
9476
+ fail("an area emitter needs a horizontal lower edge");
9477
+ }
9478
+ }
9479
+ }
9480
+ function tileflowModelImageSize(bytes, mimeType) {
9481
+ const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
9482
+ if (mimeType === "image/png") {
9483
+ if (bytes.length < 33 || view.getUint32(0) !== 2303741511 || view.getUint32(4) !== 218765834 || view.getUint32(12) !== 1229472850)
9484
+ return void 0;
9485
+ const width = view.getUint32(16), height = view.getUint32(20);
9486
+ return width && height ? [width, height] : void 0;
9487
+ }
9488
+ if (bytes.length < 4 || view.getUint16(0) !== 65496) return void 0;
9489
+ for (let at = 2; at + 4 <= bytes.length; ) {
9490
+ if (bytes[at++] !== 255) return void 0;
9491
+ while (bytes[at] === 255) at++;
9492
+ const marker = bytes[at++];
9493
+ if (marker === 217 || marker === 218) return void 0;
9494
+ if (marker === 1 || marker >= 208 && marker <= 215) continue;
9495
+ if (at + 2 > bytes.length) return void 0;
9496
+ const length = view.getUint16(at);
9497
+ if (length < 2 || at + length > bytes.length) return void 0;
9498
+ if ([192, 193, 194, 195, 197, 198, 199, 201, 202, 203, 205, 206, 207].includes(
9499
+ marker
9500
+ )) {
9501
+ if (length < 8) return void 0;
9502
+ const height = view.getUint16(at + 3), width = view.getUint16(at + 5);
9503
+ return width && height ? [width, height] : void 0;
9504
+ }
9505
+ at += length;
9506
+ }
9507
+ return void 0;
9508
+ }
9509
+
9212
9510
  // src/models-3d-file.ts
9213
9511
  var levelNames = ["near", "mid", "far"];
9214
9512
  var meshopt = "EXT_meshopt_compression";
9215
9513
  var quantisation = "KHR_mesh_quantization";
9216
9514
  var partCount = Object.keys(tileflowModelParts).length;
9515
+ var roleCount = Object.keys(tileflowModelLightingRoles).length;
9217
9516
  var modelId2 = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
9218
9517
  var [glbMagic, jsonChunk, binChunk] = [1179937895, 1313821514, 5130562];
9219
9518
  var [short, byte, unsignedByte, unsignedShort, unsignedInt] = [5122, 5120, 5121, 5123, 5125];
@@ -9249,8 +9548,7 @@ async function decodeTileflowModels(buffer, decompressor) {
9249
9548
  if (source !== 0 || byteStride !== stride || count * byteStride !== length)
9250
9549
  throw new Error();
9251
9550
  if (filter !== void 0 && filter !== "NONE") throw new Error();
9252
- if (mode !== (stride === tileflowModelVertexBytes ? "ATTRIBUTES" : "TRIANGLES"))
9253
- throw new Error();
9551
+ if (mode !== (stride > 4 ? "ATTRIBUTES" : "TRIANGLES")) throw new Error();
9254
9552
  out = new Uint8Array(length);
9255
9553
  decoder.decodeGltfBuffer(out, count, byteStride, slice(bin, byteOffset, byteLength), mode);
9256
9554
  } else {
@@ -9266,6 +9564,8 @@ async function decodeTileflowModels(buffer, decompressor) {
9266
9564
  for (const root of roots) {
9267
9565
  const node = json.nodes?.[root];
9268
9566
  const model = readModelNode(node);
9567
+ const roles = node.extras.tileflow.lightingRoles;
9568
+ if (roles !== void 0 && roles !== 1) return void 0;
9269
9569
  const children = Array.isArray(node.children) ? node.children : [];
9270
9570
  if (children.length < 1 || children.length > tileflowModelLimits.levels) return void 0;
9271
9571
  const levels = [];
@@ -9275,7 +9575,8 @@ async function decodeTileflowModels(buffer, decompressor) {
9275
9575
  json,
9276
9576
  json.nodes?.[child],
9277
9577
  levelNames[at],
9278
- viewBytes
9578
+ viewBytes,
9579
+ roles === 1
9279
9580
  );
9280
9581
  const low = level.min.map((value, axis) => level.centre[axis] + value * level.step);
9281
9582
  const high = level.max.map((value, axis) => level.centre[axis] + value * level.step);
@@ -9288,7 +9589,8 @@ async function decodeTileflowModels(buffer, decompressor) {
9288
9589
  vertices: level.vertices,
9289
9590
  indices: level.indices,
9290
9591
  centre: level.centre,
9291
- step: level.step
9592
+ step: level.step,
9593
+ ...level.surface ? { surface: level.surface } : {}
9292
9594
  });
9293
9595
  }
9294
9596
  models.push({
@@ -9338,7 +9640,7 @@ function readModelNode(node) {
9338
9640
  ...own.heading !== void 0 ? { heading: own.heading } : {}
9339
9641
  };
9340
9642
  }
9341
- function readLevelNode(json, node, name, viewBytes) {
9643
+ function readLevelNode(json, node, name, viewBytes, hasLightingRoles) {
9342
9644
  if (node?.name !== name || node.matrix !== void 0 || node.rotation !== void 0)
9343
9645
  throw new Error();
9344
9646
  const centre = node.translation;
@@ -9378,18 +9680,51 @@ function readLevelNode(json, node, name, viewBytes) {
9378
9680
  tileflowModelVertexBytes,
9379
9681
  count * tileflowModelVertexBytes
9380
9682
  );
9381
- for (let vertex = 0; vertex < count; vertex += 1)
9382
- if (vertices[vertex * tileflowModelVertexBytes + 12] >= partCount) throw new Error();
9683
+ for (let vertex = 0; vertex < count; vertex += 1) {
9684
+ const at = vertex * tileflowModelVertexBytes;
9685
+ const part = vertices[at + 12];
9686
+ if (part >= partCount) throw new Error();
9687
+ if (!hasLightingRoles) vertices[at + 15] = tileflowModelDefaultLightingRoles[part];
9688
+ else if (vertices[at + 15] >= roleCount) throw new Error();
9689
+ }
9383
9690
  const indexBytes = viewBytes(index.bufferView, width, indexCount * width);
9384
9691
  const indices = width === 4 ? new Uint32Array(indexBytes.buffer, indexBytes.byteOffset, indexCount) : new Uint16Array(indexBytes.buffer, indexBytes.byteOffset, indexCount);
9385
9692
  for (const vertex of indices) if (vertex >= count) throw new Error();
9693
+ const physical = node.extras?.tileflow?.surface;
9694
+ let surface;
9695
+ if (physical !== void 0) {
9696
+ if (physical?.version !== 1 || !Array.isArray(physical.attributes) || physical.attributes.length !== 2)
9697
+ throw new Error();
9698
+ const uv = accessor(physical.attributes[0]);
9699
+ const colour = accessor(physical.attributes[1]);
9700
+ if (!uv || !colour || (uv.byteOffset ?? 0) !== 0 || colour.byteOffset !== 16 || uv.bufferView !== colour.bufferView || [uv, colour].some(
9701
+ (value) => value.componentType !== 5126 || value.type !== "VEC4" || value.count !== count || value.normalized === true
9702
+ ))
9703
+ throw new Error();
9704
+ const attributeBytes = viewBytes(uv.bufferView, 32, count * 32);
9705
+ if (!Array.isArray(physical.textures) || physical.textures.length > 32) throw new Error();
9706
+ surface = {
9707
+ attributes: new Float32Array(attributeBytes.buffer, attributeBytes.byteOffset, count * 8),
9708
+ materials: physical.materials,
9709
+ ranges: physical.ranges,
9710
+ textures: physical.textures.map((texture) => {
9711
+ const { bufferView, byteLength, ...properties } = texture;
9712
+ if (!Number.isInteger(byteLength) || byteLength <= 0 || byteLength > tileflowModelLimits.bytes)
9713
+ throw new Error();
9714
+ return { ...properties, bytes: viewBytes(bufferView, 1, byteLength) };
9715
+ }),
9716
+ ...physical.emitters === void 0 ? {} : { emitters: physical.emitters }
9717
+ };
9718
+ checkTileflowModelSurface(surface, count, indexCount);
9719
+ }
9386
9720
  return {
9387
9721
  vertices,
9388
9722
  indices,
9389
9723
  centre: [centre[0], centre[1], centre[2]],
9390
9724
  step: scale[0],
9391
9725
  min,
9392
- max: max2
9726
+ max: max2,
9727
+ surface
9393
9728
  };
9394
9729
  }
9395
9730
  function readGlb(bytes) {
@@ -9429,12 +9764,37 @@ function placed(at) {
9429
9764
  var tileflowLandmarkTileZoom = 14;
9430
9765
  var tileflowLandmarkRegionZoom = 6;
9431
9766
  var sha256 = /^[a-f0-9]{64}$/u;
9767
+ var landmarkId = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
9768
+ var footprintStep = 10;
9769
+ function tileflowLandmarkTileOf(at) {
9770
+ const [longitude, latitude] = at;
9771
+ if (!(longitude >= -180 && longitude <= 180 && latitude >= -85 && latitude <= 85))
9772
+ throw new RangeError("A landmark stands at a longitude and a latitude on the map.");
9773
+ const n = 2 ** tileflowLandmarkTileZoom;
9774
+ const phi = latitude * Math.PI / 180;
9775
+ return [
9776
+ Math.min(n - 1, Math.floor((longitude + 180) / 360 * n)),
9777
+ Math.floor((1 - Math.log(Math.tan(phi) + 1 / Math.cos(phi)) / Math.PI) / 2 * n)
9778
+ ];
9779
+ }
9432
9780
  function tileflowLandmarkRegionOf(tile) {
9433
9781
  const shift = 2 ** (tileflowLandmarkTileZoom - tileflowLandmarkRegionZoom);
9434
9782
  return [Math.floor(tile[0] / shift), Math.floor(tile[1] / shift)];
9435
9783
  }
9784
+ function validFootprint(footprint) {
9785
+ if (!Array.isArray(footprint) || footprint.length === 0) return false;
9786
+ let points = 0;
9787
+ for (const ring of footprint) {
9788
+ if (!Array.isArray(ring) || ring.length < 6 || ring.length % 2 !== 0) return false;
9789
+ if (!ring.every((value) => Number.isSafeInteger(value))) return false;
9790
+ points += ring.length / 2;
9791
+ }
9792
+ return points <= tileflowModelLimits.footprintPoints;
9793
+ }
9436
9794
  function readTileflowLandmarkRegion(json) {
9437
- if (!isRecord8(json) || json.format !== "tileflow-landmark-region-v1") return void 0;
9795
+ if (!isRecord8(json)) return void 0;
9796
+ const named = json.format === "tileflow-landmark-region-v2";
9797
+ if (!named && json.format !== "tileflow-landmark-region-v1") return void 0;
9438
9798
  const region = json.region;
9439
9799
  if (json.zoom !== tileflowLandmarkTileZoom || !Array.isArray(region) || region.length !== 3 || region[0] !== tileflowLandmarkRegionZoom || !Array.isArray(json.tiles))
9440
9800
  return void 0;
@@ -9442,17 +9802,50 @@ function readTileflowLandmarkRegion(json) {
9442
9802
  if (!Number.isInteger(x3) || !Number.isInteger(y2)) return void 0;
9443
9803
  const tiles = [];
9444
9804
  for (const tile of json.tiles) {
9445
- if (!Array.isArray(tile) || tile.length !== 3) return void 0;
9446
- const [tx, ty, hash4] = tile;
9805
+ if (!Array.isArray(tile) || tile.length !== (named ? 4 : 3)) return void 0;
9806
+ const [tx, ty, hash4, listed] = tile;
9447
9807
  if (!Number.isInteger(tx) || !Number.isInteger(ty) || typeof hash4 !== "string")
9448
9808
  return void 0;
9449
9809
  if (!sha256.test(hash4)) return void 0;
9450
9810
  const [rx, ry] = tileflowLandmarkRegionOf([tx, ty]);
9451
9811
  if (rx !== x3 || ry !== y2) return void 0;
9452
- tiles.push([tx, ty, hash4]);
9812
+ if (!named) {
9813
+ tiles.push([tx, ty, hash4]);
9814
+ continue;
9815
+ }
9816
+ const landmarks = readLandmarks(listed, tx, ty);
9817
+ if (!landmarks) return void 0;
9818
+ tiles.push([tx, ty, hash4, landmarks]);
9453
9819
  }
9454
9820
  return { region: [x3, y2], tiles };
9455
9821
  }
9822
+ function readLandmarks(listed, tx, ty) {
9823
+ if (!Array.isArray(listed) || listed.length === 0) return void 0;
9824
+ const out = [];
9825
+ for (const landmark of listed) {
9826
+ if (!isRecord8(landmark)) return void 0;
9827
+ const { id, at, heading, footprint } = landmark;
9828
+ if (typeof id !== "string" || !landmarkId.test(id)) return void 0;
9829
+ if (!Array.isArray(at) || at.length !== 2 || !at.every((value) => typeof value === "number"))
9830
+ return void 0;
9831
+ if (!(typeof heading === "number" && heading >= 0 && heading < 360)) return void 0;
9832
+ if (!validFootprint(footprint)) return void 0;
9833
+ let own;
9834
+ try {
9835
+ own = tileflowLandmarkTileOf([at[0], at[1]]);
9836
+ } catch {
9837
+ return void 0;
9838
+ }
9839
+ if (own[0] !== tx || own[1] !== ty) return void 0;
9840
+ out.push({
9841
+ id,
9842
+ at: [at[0], at[1]],
9843
+ heading,
9844
+ footprint: footprint.map((ring) => ring.map((value) => value / footprintStep))
9845
+ });
9846
+ }
9847
+ return out;
9848
+ }
9456
9849
  function readTileflowLandmarksTileJson(json, url) {
9457
9850
  if (!isRecord8(json) || json.format !== "tileflow-landmarks-v2") return void 0;
9458
9851
  const tile = Array.isArray(json.tiles) ? json.tiles[0] : void 0;
@@ -9479,16 +9872,266 @@ function isRecord8(value) {
9479
9872
  return typeof value === "object" && value !== null && !Array.isArray(value);
9480
9873
  }
9481
9874
 
9875
+ // src/models-3d-emitters.ts
9876
+ function modelEmitterGeometry(emitter) {
9877
+ const [a, b2] = [emitter.start, emitter.end];
9878
+ const width = Math.hypot(b2[0] - a[0], b2[1] - a[1]);
9879
+ const tangent = [(b2[0] - a[0]) / width, (b2[1] - a[1]) / width];
9880
+ const normal = [tangent[1], -tangent[0]];
9881
+ const reach = Math.min(10, Math.max(4, emitter.height * 1.3));
9882
+ const depth = emitter.depth ?? 0;
9883
+ const spread = depth > 0 ? Math.min(0.29, width * 0.1 / depth) : 0.29;
9884
+ const half = Math.max(5e-3, width / 2 - depth * (spread + 0.01));
9885
+ const centre = [(a[0] + b2[0]) / 2, (a[1] + b2[1]) / 2, a[2]];
9886
+ const point = (along, out, up) => [
9887
+ centre[0] + tangent[0] * along + normal[0] * out,
9888
+ centre[1] + tangent[1] * along + normal[1] * out,
9889
+ centre[2] + up
9890
+ ];
9891
+ const left = point(-half, 0, 0.1), right = point(half, 0, 0.1);
9892
+ const farLeft = point(-half - spread * reach, reach, 0);
9893
+ const farRight = point(half + spread * reach, reach, 0);
9894
+ const positions = new Float32Array([
9895
+ ...left,
9896
+ ...right,
9897
+ ...farLeft,
9898
+ ...farRight,
9899
+ ...point(-half, 0, emitter.height),
9900
+ ...point(half, 0, emitter.height),
9901
+ ...left,
9902
+ ...right,
9903
+ ...farLeft,
9904
+ ...farRight
9905
+ ]);
9906
+ const h2 = half / reach;
9907
+ const coordinates = new Float32Array([
9908
+ -h2,
9909
+ 0,
9910
+ h2,
9911
+ 1.3,
9912
+ h2,
9913
+ 0,
9914
+ h2,
9915
+ 1.3,
9916
+ -h2 - spread,
9917
+ 1,
9918
+ h2,
9919
+ 0.8,
9920
+ h2 + spread,
9921
+ 1,
9922
+ h2,
9923
+ 0.8,
9924
+ h2 + spread,
9925
+ 0.8,
9926
+ h2,
9927
+ 0.7,
9928
+ h2 + spread,
9929
+ 0.8,
9930
+ h2,
9931
+ 0.7,
9932
+ 0,
9933
+ 0,
9934
+ h2,
9935
+ 1.3,
9936
+ 0,
9937
+ 0,
9938
+ h2,
9939
+ 1.3,
9940
+ h2 + spread,
9941
+ 1.2,
9942
+ h2,
9943
+ 0.8,
9944
+ h2 + spread,
9945
+ 1.2,
9946
+ h2,
9947
+ 0.8
9948
+ ]);
9949
+ return { positions, coordinates, reach };
9950
+ }
9951
+ var modelEmitterVertex = (projected) => `#version 300 es
9952
+ ${projected}
9953
+ uniform vec3 u_emitter_positions[10];
9954
+ uniform vec4 u_emitter_coordinates[10];
9955
+ uniform vec2 u_heading;
9956
+ uniform vec2 u_anchor;
9957
+ uniform float u_model_metres;
9958
+ uniform float u_scale;
9959
+ out vec4 v_light_coordinates;
9960
+ void main() {
9961
+ const int indices[12] = int[12](0, 1, 2, 2, 1, 3, 4, 6, 8, 5, 9, 7);
9962
+ int index = indices[gl_VertexID];
9963
+ vec3 position = u_emitter_positions[index];
9964
+ vec2 turned = vec2(position.x * u_heading.x + position.y * u_heading.y,
9965
+ -position.x * u_heading.y + position.y * u_heading.x);
9966
+ float growth = clamp(u_scale, 0.0, 1.0);
9967
+ gl_Position = projectTileFor3D(u_anchor + turned * vec2(1.0, -1.0) / u_model_metres,
9968
+ position.z * growth * growth * (3.0 - 2.0 * growth));
9969
+ v_light_coordinates = u_emitter_coordinates[index];
9970
+ }`;
9971
+ var modelEmitterFragment = `#version 300 es
9972
+ precision highp float;
9973
+ in vec4 v_light_coordinates;
9974
+ uniform vec3 u_emitter_colour;
9975
+ uniform float u_emitter_strength;
9976
+ uniform float u_opacity;
9977
+ out vec4 fragColor;
9978
+ void main() {
9979
+ if (u_emitter_strength <= 0.0) discard;
9980
+ vec4 light = v_light_coordinates;
9981
+ float across = max(0.0, abs(light.x) - light.z) * 1.3;
9982
+ float distance = length(vec2(across, light.y)) +
9983
+ 0.5 * (1.0 - clamp(u_emitter_strength - 1.0, 0.0, 1.0));
9984
+ float alpha = clamp((1.0 - distance * distance) * light.w, 0.0, 1.0);
9985
+ fragColor = vec4(u_emitter_colour, alpha * min(1.0, u_emitter_strength) * u_opacity);
9986
+ }`;
9987
+
9988
+ // src/models-3d-material-shaders.ts
9989
+ var modelMaterialSampling = `
9990
+ in vec4 v_uv;
9991
+ in vec4 v_rgba;
9992
+ uniform bool u_physical;
9993
+ uniform vec4 u_base_colour;
9994
+ uniform int u_alpha_mode; // opaque, mask, blend
9995
+ uniform float u_alpha_cutoff;
9996
+ uniform float u_texture_enabled[5];
9997
+ uniform vec4 u_texture_transform[5]; // offset.xy, scale.xy
9998
+ uniform float u_texture_rotation[5];
9999
+ uniform float u_texture_coord[5];
10000
+ uniform sampler2D u_material_texture0;
10001
+ uniform sampler2D u_material_texture1;
10002
+ uniform sampler2D u_material_texture2;
10003
+ uniform sampler2D u_material_texture3;
10004
+ uniform sampler2D u_material_texture4;
10005
+
10006
+ vec2 materialUV(int channel) {
10007
+ vec2 uv = u_texture_coord[channel] > 0.5 ? v_uv.zw : v_uv.xy;
10008
+ vec4 transform = u_texture_transform[channel];
10009
+ uv *= transform.zw;
10010
+ float angle = u_texture_rotation[channel];
10011
+ return transform.xy + mat2(cos(angle), sin(angle), -sin(angle), cos(angle)) * uv;
10012
+ }
10013
+
10014
+ vec4 materialTexture(int channel) {
10015
+ if (u_texture_enabled[channel] < 0.5) return vec4(1.0);
10016
+ vec2 uv = materialUV(channel);
10017
+ if (channel == 0) return texture(u_material_texture0, uv);
10018
+ if (channel == 1) return texture(u_material_texture1, uv);
10019
+ if (channel == 2) return texture(u_material_texture2, uv);
10020
+ if (channel == 3) return texture(u_material_texture3, uv);
10021
+ return texture(u_material_texture4, uv);
10022
+ }
10023
+
10024
+ float materialAlpha() {
10025
+ if (!u_physical || u_alpha_mode == 0) return 1.0;
10026
+ float alpha = u_base_colour.a * v_rgba.a * materialTexture(0).a;
10027
+ if (u_alpha_mode == 1 && alpha < u_alpha_cutoff) discard;
10028
+ return u_alpha_mode == 1 ? 1.0 : alpha;
10029
+ }
10030
+ `;
10031
+ var modelPhysicalLighting = `
10032
+ in vec3 v_position;
10033
+ flat in int v_part;
10034
+ uniform vec3 u_view_position;
10035
+ uniform float u_metallic;
10036
+ uniform float u_roughness;
10037
+ uniform vec3 u_emissive;
10038
+ uniform float u_normal_strength;
10039
+ uniform float u_occlusion_strength;
10040
+ uniform bool u_unlit;
10041
+ uniform vec3 u_part_colours[5];
10042
+ uniform float u_part_mix[5];
10043
+ uniform vec4 u_role_colour[7];
10044
+ uniform vec4 u_role_material[7]; // metallic, roughness, their respective override weights
10045
+
10046
+ vec3 fromSRGB(vec3 colour) {
10047
+ return pow(max(colour, vec3(0.0)), vec3(2.2));
10048
+ }
10049
+
10050
+ vec3 toSRGB(vec3 colour) {
10051
+ // Use the scene's gamma-2.2 approximation, including at low night radiance.
10052
+ return pow(max(colour, vec3(0.0)), vec3(1.0 / 2.2));
10053
+ }
10054
+
10055
+ vec3 materialNormal(vec3 normal) {
10056
+ if (u_texture_enabled[2] < 0.5) return normal;
10057
+ vec2 uv = materialUV(2);
10058
+ vec2 dx = dFdx(uv), dy = dFdy(uv);
10059
+ float determinant = dx.x * dy.y - dx.y * dy.x;
10060
+ if (abs(determinant) < 1e-8) return normal;
10061
+ vec3 px = dFdx(v_position), py = dFdy(v_position);
10062
+ vec3 tangent = (px * dy.y - py * dx.y) / determinant;
10063
+ vec3 bitangent = (py * dx.x - px * dy.x) / determinant;
10064
+ tangent = normalize(tangent - normal * dot(normal, tangent));
10065
+ bitangent = normalize(bitangent - normal * dot(normal, bitangent));
10066
+ vec3 sampleNormal = materialTexture(2).xyz * 2.0 - 1.0;
10067
+ sampleNormal.xy *= u_normal_strength;
10068
+ return normalize(mat3(tangent, bitangent, normal) * sampleNormal);
10069
+ }
10070
+
10071
+ vec3 materialBase(int role) {
10072
+ vec4 own = u_role_colour[role];
10073
+ vec3 factor = mix(u_base_colour.rgb, fromSRGB(own.rgb), own.a);
10074
+ factor = mix(factor, fromSRGB(u_part_colours[v_part]), u_part_mix[v_part]);
10075
+ return factor * v_rgba.rgb * fromSRGB(materialTexture(0).rgb);
10076
+ }
10077
+
10078
+ vec3 physicalLight(vec3 base, vec3 normal, float lit, int role) {
10079
+ if (u_unlit) return base;
10080
+ vec4 packed = materialTexture(1);
10081
+ vec4 overrideMaterial = u_role_material[role];
10082
+ float metallic = clamp(mix(u_metallic, overrideMaterial.x, overrideMaterial.z) * packed.b, 0.0, 1.0);
10083
+ float roughness = clamp(mix(u_roughness, overrideMaterial.y, overrideMaterial.w) * packed.g, 0.04, 1.0);
10084
+ vec3 view = normalize(u_view_position - v_position);
10085
+ vec3 halfway = normalize(view + u_sun_dir);
10086
+ float nv = max(dot(normal, view), 0.001);
10087
+ float nl = max(dot(normal, u_sun_dir), 0.0);
10088
+ float nh = max(dot(normal, halfway), 0.0);
10089
+ float vh = max(dot(view, halfway), 0.0);
10090
+ float a2 = pow(roughness, 4.0);
10091
+ float denominator = nh * nh * (a2 - 1.0) + 1.0;
10092
+ float distribution = a2 / max(3.14159265 * denominator * denominator, 1e-7);
10093
+ float masking = 0.5 / max(nv * sqrt(a2 + (1.0 - a2) * nl * nl)
10094
+ + nl * sqrt(a2 + (1.0 - a2) * nv * nv), 1e-5);
10095
+ vec3 f0 = mix(vec3(0.04), base, metallic);
10096
+ vec3 fresnel = f0 + (1.0 - f0) * pow(1.0 - vh, 5.0);
10097
+ vec3 diffuse = base * (1.0 - metallic) * 0.96;
10098
+ // Scene light is cartographic radiance: a Lambert surface uses unit diffuse response.
10099
+ vec3 direct = (diffuse * (1.0 - fresnel) + distribution * masking * fresnel)
10100
+ * u_sun_light * nl * (1.0 - u_shadow * (1.0 - lit));
10101
+ // Analytic fit of the integrated GGX BRDF: reflection scales and biases its normal reflectance.
10102
+ float smoothness = 1.0 - roughness;
10103
+ float fit = smoothness * min(smoothness * smoothness, exp2(-9.28 * nv)) + 0.0425 - 0.0275 * roughness;
10104
+ float reflectionScale = 1.04 - 0.572 * roughness - 1.04 * fit;
10105
+ float reflectionBias = 0.022 * roughness - 0.04 + 1.04 * fit;
10106
+ vec3 reflectedSky = max(vec3(0.0), f0 * reflectionScale + reflectionBias);
10107
+ float directional = dot(u_sun_light, vec3(0.2126, 0.7152, 0.0722));
10108
+ float exposure = (0.96 + 0.04 * clamp(normal.z, -1.0, 1.0)) *
10109
+ (1.0 - 0.3 * min(directional, 1.0) * max(-dot(normal, u_sun_dir), 0.0));
10110
+ float ao = mix(1.0, v_light * mix(1.0, materialTexture(3).r, u_occlusion_strength),
10111
+ u_ambient_occlusion);
10112
+ vec3 ambient = (diffuse + reflectedSky) * u_sky_light * exposure * ao;
10113
+ return ambient + min(direct, vec3(1.0));
10114
+ }
10115
+ `;
10116
+
9482
10117
  // src/models-3d-shaders.ts
9483
10118
  var tileflowModelAttributes = [
9484
10119
  ["a_position", 3, "SHORT", false, 0],
9485
10120
  ["a_normal", 3, "BYTE", true, 8],
9486
- ["a_paint", 3, "UNSIGNED_BYTE", false, 12]
10121
+ ["a_paint", 3, "UNSIGNED_BYTE", false, 12],
10122
+ ["a_lighting_role", 1, "UNSIGNED_BYTE", false, 15]
10123
+ ];
10124
+ var tileflowModelSurfaceAttributes = [
10125
+ ["a_uv", 4, "FLOAT", false, 0],
10126
+ ["a_rgba", 4, "FLOAT", false, 16]
9487
10127
  ];
9488
10128
  var attributes2 = `
9489
10129
  in vec3 a_position; // whole steps from the level's centre, in glTF's axes
9490
10130
  in vec3 a_normal; // in glTF's axes
9491
10131
  in vec3 a_paint; // part; shade (0 to 255); light (0 to 255)
10132
+ in float a_lighting_role;
10133
+ in vec4 a_uv;
10134
+ in vec4 a_rgba;
9492
10135
  `;
9493
10136
  var placement = `
9494
10137
  uniform vec3 u_level_centre; // in glTF's axes, in metres
@@ -9530,13 +10173,22 @@ var tileflowModelDepthVertex = `#version 300 es
9530
10173
  ${attributes2}
9531
10174
  uniform mat4 u_light_matrix;
9532
10175
  ${placement}
10176
+ out vec4 v_uv;
10177
+ out vec4 v_rgba;
9533
10178
  void main() {
9534
10179
  vec3 m = turned(metresOf());
9535
10180
  gl_Position = u_light_matrix * vec4(sceneOf(tileOf(m), grown(m.z)), 1.0);
10181
+ v_uv = a_uv;
10182
+ v_rgba = a_rgba;
9536
10183
  }`;
9537
10184
  var tileflowModelDepthFragment = `#version 300 es
9538
10185
  precision highp float;
9539
- void main() {}`;
10186
+ ${modelMaterialSampling}
10187
+ void main() {
10188
+ float alpha = materialAlpha();
10189
+ // Blended surfaces cast an alpha-tested solar shadow, avoiding solid panes.
10190
+ if (u_alpha_mode == 2 && alpha < 0.5) discard;
10191
+ }`;
9540
10192
  var tileflowModelVertex = (projected) => `#version 300 es
9541
10193
  ${projected}
9542
10194
  ${attributes2}
@@ -9544,11 +10196,18 @@ uniform mat4 u_light_matrix;
9544
10196
  uniform vec3 u_shadow_dir;
9545
10197
  uniform float u_normal_offset;
9546
10198
  uniform vec3 u_part_colours[5];
10199
+ uniform vec2 u_model_height;
9547
10200
  ${placement}
9548
10201
  out vec3 v_colour;
9549
10202
  out vec3 v_normal;
9550
10203
  out vec3 v_shadow;
9551
10204
  out float v_light;
10205
+ flat out int v_lighting_role;
10206
+ out float v_height;
10207
+ out vec4 v_uv;
10208
+ out vec4 v_rgba;
10209
+ out vec3 v_position;
10210
+ flat out int v_part;
9552
10211
 
9553
10212
  void main() {
9554
10213
  vec3 m = turned(metresOf());
@@ -9561,10 +10220,16 @@ void main() {
9561
10220
  v_shadow = shadow.xyz * 0.5 + 0.5;
9562
10221
  v_normal = normal;
9563
10222
  int part = int(a_paint.x + 0.5);
10223
+ v_part = clamp(part, 0, 4);
10224
+ v_uv = a_uv;
10225
+ v_rgba = a_rgba;
10226
+ v_position = vec3(m.xy, z);
9564
10227
  vec3 colour = u_part_colours[clamp(part, 0, 4)];
9565
10228
  // A material lighter or darker than mid-grey makes its part lighter or darker.
9566
10229
  v_colour = colour * (0.55 + 0.45 * (a_paint.y / 255.0) * 2.0);
9567
10230
  v_light = a_paint.z / 255.0;
10231
+ v_lighting_role = clamp(int(a_lighting_role + 0.5), 0, 6);
10232
+ v_height = (m.z - u_model_height.x) / max(u_model_height.y, 0.001);
9568
10233
  }`;
9569
10234
  var tileflowModelFragment = `#version 300 es
9570
10235
  precision highp float;
@@ -9572,18 +10237,29 @@ in vec3 v_colour;
9572
10237
  in vec3 v_normal;
9573
10238
  in vec3 v_shadow;
9574
10239
  in float v_light;
10240
+ flat in int v_lighting_role;
10241
+ in float v_height;
9575
10242
  uniform vec3 u_sun_dir;
9576
10243
  uniform vec3 u_sun_light;
9577
10244
  uniform vec3 u_sky_light;
9578
10245
  uniform float u_night;
10246
+ uniform float u_ambient_occlusion;
10247
+ uniform float u_role_emission[7];
10248
+ uniform vec4 u_role_emission_colour[7];
10249
+ uniform vec4 u_role_emission_gradient[7];
10250
+ uniform float u_role_emission_power[7];
10251
+ uniform float u_opacity;
9579
10252
  out vec4 fragColor;
9580
10253
  ${tileflowShadowLookup}
10254
+ ${modelMaterialSampling}
10255
+ ${modelPhysicalLighting}
9581
10256
 
9582
10257
  // The buildings' light: a sunlit roof keeps its colour, a face turned to a low sun glows, and shade
9583
10258
  // takes the sky's colour; a face that looks down sees little sky.
9584
10259
  vec3 shade(vec3 normal, float lit) {
9585
10260
  float facing = dot(normal, u_sun_dir);
9586
10261
  vec3 sky = u_sky_light * mix(mix(0.8, 0.5, u_night), 1.0, max(normal.z, -0.4));
10262
+ sky *= mix(1.0, v_light, u_ambient_occlusion);
9587
10263
  vec3 light = sky + u_sun_light * max(facing, 0.0) * (1.0 - u_shadow * (1.0 - lit));
9588
10264
  vec3 roof = u_sky_light + u_sun_light * max(u_sun_dir.z, 0.0);
9589
10265
  return min(pow(light / max(roof, vec3(1e-4)), vec3(1.0 / 2.2)), vec3(1.7));
@@ -9591,10 +10267,171 @@ vec3 shade(vec3 normal, float lit) {
9591
10267
 
9592
10268
  void main() {
9593
10269
  vec3 normal = normalize(gl_FrontFacing ? v_normal : -v_normal);
10270
+ if (u_physical) normal = materialNormal(normal);
9594
10271
  float lit = dot(normal, u_sun_dir) > 0.0 ? visibility(v_shadow, 0.3) : 0.0;
9595
- fragColor = vec4(v_colour * shade(normal, lit) * mix(0.55, 1.0, v_light), 1.0);
10272
+ int role = clamp(v_lighting_role, 0, 6);
10273
+ vec4 gradient = u_role_emission_gradient[role];
10274
+ float multiplier = 1.0;
10275
+ if (gradient.x != gradient.y) {
10276
+ float height = clamp((v_height - gradient.x) / (gradient.y - gradient.x), 0.0, 1.0);
10277
+ multiplier = mix(gradient.z, gradient.w, pow(height, pow(10.0, u_role_emission_power[role])));
10278
+ }
10279
+ float emission = clamp(u_role_emission[role] * multiplier, 0.0, 1.0);
10280
+ if (u_physical) {
10281
+ float alpha = materialAlpha();
10282
+ vec3 base = materialBase(role);
10283
+ vec3 radiance = physicalLight(base, normal, lit, role);
10284
+ radiance += u_emissive * fromSRGB(materialTexture(4).rgb);
10285
+ vec4 own = u_role_emission_colour[role];
10286
+ radiance = mix(radiance, mix(base, fromSRGB(own.rgb), own.a), emission);
10287
+ fragColor = vec4(toSRGB(radiance), alpha * u_opacity);
10288
+ return;
10289
+ }
10290
+ vec3 colour = v_colour * shade(normal, lit);
10291
+ if (emission > 0.0) {
10292
+ vec4 own = u_role_emission_colour[role];
10293
+ vec3 target = mix(v_colour, own.rgb, own.a);
10294
+ colour = pow(mix(pow(max(colour, vec3(0.0)), vec3(2.2)),
10295
+ pow(max(target, vec3(0.0)), vec3(2.2)), emission), vec3(1.0 / 2.2));
10296
+ }
10297
+ fragColor = vec4(colour, 1.0);
9596
10298
  }`;
9597
10299
 
10300
+ // src/models-3d-materials-browser.ts
10301
+ async function readModelImages(model, signal) {
10302
+ const buffers = /* @__PURE__ */ new Map();
10303
+ const pending = /* @__PURE__ */ new Map();
10304
+ const levels = model.levels.map(
10305
+ (level) => (level.surface?.textures ?? []).map((texture) => {
10306
+ const { bytes } = texture;
10307
+ if (!buffers.has(bytes.buffer)) buffers.set(bytes.buffer, buffers.size);
10308
+ const key = `${buffers.get(bytes.buffer)}:${bytes.byteOffset}:${bytes.byteLength}`;
10309
+ let image = pending.get(key);
10310
+ if (!image) {
10311
+ image = createImageBitmap(new Blob([bytes.slice().buffer], { type: texture.mimeType }), {
10312
+ imageOrientation: "none",
10313
+ premultiplyAlpha: "none",
10314
+ colorSpaceConversion: "none"
10315
+ }).then((bitmap) => {
10316
+ if (bitmap.width !== texture.width || bitmap.height !== texture.height) {
10317
+ bitmap.close();
10318
+ throw new RangeError("Model texture dimensions changed during decoding.");
10319
+ }
10320
+ return bitmap;
10321
+ });
10322
+ pending.set(key, image);
10323
+ }
10324
+ return image;
10325
+ })
10326
+ );
10327
+ const settled = await Promise.allSettled(pending.values());
10328
+ if (signal.aborted || settled.some((result) => result.status === "rejected")) {
10329
+ for (const result of settled) if (result.status === "fulfilled") result.value.close();
10330
+ signal.throwIfAborted();
10331
+ throw new RangeError("A model texture could not be decoded.");
10332
+ }
10333
+ return Promise.all(levels.map((level) => Promise.all(level)));
10334
+ }
10335
+ function closeModelImages(images) {
10336
+ for (const image of new Set(images?.flat())) image.close();
10337
+ }
10338
+ function uploadModelSurface(gl, level, images) {
10339
+ if (!level.surface) return void 0;
10340
+ const vbo = gl.createBuffer();
10341
+ gl.bindBuffer(gl.ARRAY_BUFFER, vbo);
10342
+ gl.bufferData(gl.ARRAY_BUFFER, level.surface.attributes, gl.STATIC_DRAW);
10343
+ for (const [
10344
+ index,
10345
+ [, size, type, normalised, offset]
10346
+ ] of tileflowModelSurfaceAttributes.entries()) {
10347
+ const location = index + tileflowModelAttributes.length;
10348
+ gl.enableVertexAttribArray(location);
10349
+ gl.vertexAttribPointer(location, size, gl[type], normalised, 32, offset);
10350
+ }
10351
+ const flip = gl.getParameter(gl.UNPACK_FLIP_Y_WEBGL);
10352
+ const premultiply = gl.getParameter(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL);
10353
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
10354
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
10355
+ try {
10356
+ const textures = level.surface.textures.map((source, index) => {
10357
+ const texture = gl.createTexture();
10358
+ gl.bindTexture(gl.TEXTURE_2D, texture);
10359
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, source.wrapS ?? gl.REPEAT);
10360
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, source.wrapT ?? gl.REPEAT);
10361
+ const min = source.minFilter ?? gl.LINEAR_MIPMAP_LINEAR;
10362
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, min);
10363
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, source.magFilter ?? gl.LINEAR);
10364
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, gl.RGBA, gl.UNSIGNED_BYTE, images[index]);
10365
+ if (min >= gl.NEAREST_MIPMAP_NEAREST) gl.generateMipmap(gl.TEXTURE_2D);
10366
+ return texture;
10367
+ });
10368
+ const vertices = new DataView(
10369
+ level.vertices.buffer,
10370
+ level.vertices.byteOffset,
10371
+ level.vertices.byteLength
10372
+ );
10373
+ const centres = new Float32Array(level.surface.ranges.length * 3);
10374
+ for (const [index, range] of level.surface.ranges.entries()) {
10375
+ const centre = [0, 0, 0];
10376
+ for (let at = range.from; at < range.from + range.count; at++)
10377
+ for (let axis = 0; axis < 3; axis++)
10378
+ centre[axis] += level.centre[axis] + vertices.getInt16(level.indices[at] * 16 + axis * 2, true) * level.step;
10379
+ centres.set(
10380
+ [centre[0] / range.count, -centre[2] / range.count, centre[1] / range.count],
10381
+ index * 3
10382
+ );
10383
+ }
10384
+ return {
10385
+ vbo,
10386
+ textures,
10387
+ centres,
10388
+ emitters: (level.surface.emitters ?? []).map(modelEmitterGeometry)
10389
+ };
10390
+ } finally {
10391
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, flip);
10392
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, premultiply);
10393
+ }
10394
+ }
10395
+ function bindModelMaterial(gl, uniforms, material, buffers, blank) {
10396
+ const at = (name) => uniforms[name] ?? null;
10397
+ gl.uniform1i(at("u_physical"), material ? 1 : 0);
10398
+ const enabled = new Float32Array(5), transforms = new Float32Array(20);
10399
+ const rotations = new Float32Array(5), coordinates = new Float32Array(5);
10400
+ for (const [index, channel] of tileflowModelTextureChannels.entries()) {
10401
+ const ref = material?.[channel];
10402
+ gl.activeTexture(gl.TEXTURE0 + 2 + index);
10403
+ gl.bindTexture(gl.TEXTURE_2D, ref ? buffers.textures[ref.texture] : blank);
10404
+ gl.uniform1i(at(`u_material_texture${index}`), 2 + index);
10405
+ enabled[index] = ref ? 1 : 0;
10406
+ transforms.set([...ref?.offset ?? [0, 0], ...ref?.scale ?? [1, 1]], index * 4);
10407
+ rotations[index] = ref?.rotation ?? 0;
10408
+ coordinates[index] = ref?.texCoord ?? 0;
10409
+ }
10410
+ gl.uniform1fv(at("u_texture_enabled"), enabled);
10411
+ gl.uniform4fv(at("u_texture_transform"), transforms);
10412
+ gl.uniform1fv(at("u_texture_rotation"), rotations);
10413
+ gl.uniform1fv(at("u_texture_coord"), coordinates);
10414
+ gl.uniform4fv(at("u_base_colour"), material?.baseColorFactor ?? [1, 1, 1, 1]);
10415
+ gl.uniform1f(at("u_metallic"), material?.metallicFactor ?? 1);
10416
+ gl.uniform1f(at("u_roughness"), material?.roughnessFactor ?? 1);
10417
+ gl.uniform3fv(
10418
+ at("u_emissive"),
10419
+ (material?.emissiveFactor ?? [0, 0, 0]).map((c) => c * (material?.emissiveStrength ?? 1))
10420
+ );
10421
+ gl.uniform1f(at("u_normal_strength"), material?.normalTexture?.strength ?? 1);
10422
+ gl.uniform1f(at("u_occlusion_strength"), material?.occlusionTexture?.strength ?? 1);
10423
+ gl.uniform1i(at("u_unlit"), material?.unlit ? 1 : 0);
10424
+ gl.uniform1i(
10425
+ at("u_alpha_mode"),
10426
+ material?.alphaMode === "MASK" ? 1 : material?.alphaMode === "BLEND" ? 2 : 0
10427
+ );
10428
+ gl.uniform1f(at("u_alpha_cutoff"), material?.alphaCutoff ?? 0.5);
10429
+ if (material && !material.doubleSided) {
10430
+ gl.enable(gl.CULL_FACE);
10431
+ gl.cullFace(gl.BACK);
10432
+ } else gl.disable(gl.CULL_FACE);
10433
+ }
10434
+
9598
10435
  // src/models-3d-browser.ts
9599
10436
  var earth2 = 40075016686e-3;
9600
10437
  var keptModels = 12;
@@ -9604,7 +10441,11 @@ var reachTiles = 1;
9604
10441
  var levelDistances = [900, 2600];
9605
10442
  var retryAfter = 3e4;
9606
10443
  var tilesetRetryAfter = 3e5;
10444
+ var landmarkHold = 3e3;
10445
+ var settleWait = 1500;
9607
10446
  function createTileflowPlacedModels(options) {
10447
+ const holdFor = options.timing?.hold ?? landmarkHold;
10448
+ const settleFor = options.timing?.settle ?? settleWait;
9608
10449
  const loaded = /* @__PURE__ */ new Map();
9609
10450
  const loading = /* @__PURE__ */ new Map();
9610
10451
  const failed = /* @__PURE__ */ new Map();
@@ -9620,6 +10461,11 @@ function createTileflowPlacedModels(options) {
9620
10461
  const tileFailed = /* @__PURE__ */ new Map();
9621
10462
  let wantedTiles = /* @__PURE__ */ new Set();
9622
10463
  let active = [];
10464
+ let held = [];
10465
+ const holding = /* @__PURE__ */ new Map();
10466
+ let holdTimer;
10467
+ let lastLandmarks;
10468
+ const waiting = /* @__PURE__ */ new Set();
9623
10469
  let dirty = true;
9624
10470
  let ownIds = /* @__PURE__ */ new Set();
9625
10471
  let lastPlacements;
@@ -9631,7 +10477,7 @@ function createTileflowPlacedModels(options) {
9631
10477
  function tilesOf(model, zoom) {
9632
10478
  const [x3, y2] = cameraTileAt(model.placement.at[0], model.placement.at[1], zoom);
9633
10479
  const metres = earth2 * Math.cos(model.placement.at[1] * Math.PI / 180) / 2 ** zoom;
9634
- const [minX, minY, , maxX, maxY] = model.model.bounds;
10480
+ const [minX, minY, , maxX, maxY] = model.bounds;
9635
10481
  const radius = Math.hypot(Math.max(-minX, maxX), Math.max(-minY, maxY)) / metres;
9636
10482
  const out = [];
9637
10483
  for (let tx = Math.floor(x3 - radius); tx <= Math.floor(x3 + radius); tx += 1)
@@ -9654,21 +10500,35 @@ function createTileflowPlacedModels(options) {
9654
10500
  function arrived() {
9655
10501
  dirty = true;
9656
10502
  version2 += 1;
10503
+ recheck();
9657
10504
  options.changed([], lastZoom);
9658
10505
  }
10506
+ function recheck() {
10507
+ for (const waiter of waiting)
10508
+ if (waiter.settled()) {
10509
+ waiting.delete(waiter);
10510
+ waiter.resolve();
10511
+ }
10512
+ }
9659
10513
  function load(placement3, key) {
9660
10514
  const controller = new AbortController();
9661
10515
  loading.set(key, controller);
9662
- download(placement3.url, controller.signal).then(async (response) => decodeTileflowModels(await response.arrayBuffer())).then((models) => {
9663
- loading.delete(key);
10516
+ download(placement3.url, controller.signal).then(async (response) => decodeTileflowModels(await response.arrayBuffer())).then(async (models) => {
9664
10517
  if (disposed) return;
9665
10518
  const model = models?.find((entry) => entry.id === placement3.model) ?? models?.[0];
9666
10519
  if (!model) throw new Error("not a Tileflow model");
9667
- loaded.set(key, { placement: placement3, key, model, landmark: false, used: performance.now() });
10520
+ const images = await readModelImages(model, controller.signal);
10521
+ loading.delete(key);
10522
+ if (disposed || controller.signal.aborted) {
10523
+ closeModelImages(images);
10524
+ return;
10525
+ }
10526
+ loaded.set(key, { placement: placement3, key, model, images, landmark: false, used: performance.now() });
9668
10527
  arrived();
9669
10528
  }).catch(() => {
9670
10529
  loading.delete(key);
9671
10530
  failed.set(key, performance.now() + retryAfter);
10531
+ recheck();
9672
10532
  });
9673
10533
  }
9674
10534
  function loadTileset(url) {
@@ -9686,6 +10546,7 @@ function createTileflowPlacedModels(options) {
9686
10546
  if (tilesetLoad?.controller !== controller) return;
9687
10547
  tilesetLoad = void 0;
9688
10548
  tilesetFailed = { url, until: performance.now() + tilesetRetryAfter };
10549
+ recheck();
9689
10550
  });
9690
10551
  }
9691
10552
  function loadRegion(regionKey, x3, y2) {
@@ -9702,12 +10563,18 @@ function createTileflowPlacedModels(options) {
9702
10563
  const region = readTileflowLandmarkRegion(json);
9703
10564
  if (!region || region.region[0] !== x3 || region.region[1] !== y2)
9704
10565
  throw new Error("not this landmarks region");
9705
- const tiles = new Map(region.tiles.map(([tx, ty, hash4]) => [`${tx}/${ty}`, hash4]));
10566
+ const tiles = new Map(
10567
+ region.tiles.map((tile) => [
10568
+ `${tile[0]}/${tile[1]}`,
10569
+ { hash: tile[2], ...tile[3] ? { landmarks: tile[3] } : {} }
10570
+ ])
10571
+ );
9706
10572
  regions.set(regionKey, { tiles, used: performance.now() });
9707
10573
  arrived();
9708
10574
  }).catch(() => {
9709
10575
  regionLoading.delete(regionKey);
9710
10576
  regionFailed.set(regionKey, performance.now() + retryAfter);
10577
+ recheck();
9711
10578
  });
9712
10579
  }
9713
10580
  function loadTile(hash4) {
@@ -9715,22 +10582,38 @@ function createTileflowPlacedModels(options) {
9715
10582
  const url = new URL(source.tile.replaceAll("{sha256}", hash4), source.url).href;
9716
10583
  const controller = new AbortController();
9717
10584
  tileLoading.set(hash4, controller);
9718
- download(url, controller.signal).then(async (response) => decodeTileflowModels(await response.arrayBuffer())).then((models) => {
9719
- tileLoading.delete(hash4);
10585
+ download(url, controller.signal).then(async (response) => decodeTileflowModels(await response.arrayBuffer())).then(async (models) => {
9720
10586
  if (disposed || tileset !== source) return;
9721
10587
  if (!models) throw new Error("not a Tileflow landmarks tile");
9722
10588
  const now = performance.now();
9723
10589
  const entries = [];
9724
- for (const model of models) {
9725
- if (!model.at) continue;
9726
- const placement3 = { model: model.id, url, at: model.at, heading: model.heading ?? 0 };
9727
- entries.push({ placement: placement3, key: `landmark:${model.id}`, model, landmark: true, used: now });
10590
+ try {
10591
+ for (const model of models) {
10592
+ if (!model.at) continue;
10593
+ const images = await readModelImages(model, controller.signal);
10594
+ const placement3 = { model: model.id, url, at: model.at, heading: model.heading ?? 0 };
10595
+ entries.push({
10596
+ placement: placement3,
10597
+ key: `landmark:${model.id}`,
10598
+ model,
10599
+ images,
10600
+ landmark: true,
10601
+ used: now
10602
+ });
10603
+ }
10604
+ controller.signal.throwIfAborted();
10605
+ if (disposed || tileset !== source) throw new Error("The landmark tile was replaced.");
10606
+ } catch (error) {
10607
+ for (const entry of entries) closeModelImages(entry.images);
10608
+ throw error;
9728
10609
  }
10610
+ tileLoading.delete(hash4);
9729
10611
  landmarkTiles.set(hash4, { entries, used: now });
9730
10612
  arrived();
9731
10613
  }).catch(() => {
9732
10614
  tileLoading.delete(hash4);
9733
10615
  tileFailed.set(hash4, performance.now() + retryAfter);
10616
+ arrived();
9734
10617
  });
9735
10618
  }
9736
10619
  function landmarkTilesNear(tiles, zoom) {
@@ -9766,10 +10649,81 @@ function createTileflowPlacedModels(options) {
9766
10649
  }
9767
10650
  return out;
9768
10651
  }
10652
+ function holdingNow(near, own, now, shown) {
10653
+ const out = [];
10654
+ const seen = /* @__PURE__ */ new Set();
10655
+ for (const landmarks of near.values())
10656
+ for (const landmark of landmarks) {
10657
+ const key = `landmark:${landmark.id}`;
10658
+ if (seen.has(key) || ownIds.has(landmark.id)) continue;
10659
+ seen.add(key);
10660
+ const since = holding.get(key) ?? (shown ? now : void 0);
10661
+ if (since !== void 0) holding.set(key, since);
10662
+ if (since !== void 0 && now - since >= holdFor) continue;
10663
+ const entry = {
10664
+ key,
10665
+ placement: { at: landmark.at, heading: landmark.heading },
10666
+ footprint: landmark.footprint,
10667
+ bounds: boundsOf(landmark.footprint)
10668
+ };
10669
+ if (own.some((model) => footprintsMeet(entry, model))) continue;
10670
+ out.push(entry);
10671
+ }
10672
+ for (const key of [...holding.keys()]) if (!seen.has(key)) holding.delete(key);
10673
+ return out;
10674
+ }
10675
+ function scheduleHoldEnd(now) {
10676
+ if (holdTimer !== void 0) clearTimeout(holdTimer);
10677
+ holdTimer = void 0;
10678
+ let earliest = Infinity;
10679
+ for (const entry of held) {
10680
+ const since = holding.get(entry.key);
10681
+ if (since !== void 0) earliest = Math.min(earliest, since + holdFor);
10682
+ }
10683
+ if (earliest === Infinity || disposed) return;
10684
+ holdTimer = setTimeout(
10685
+ () => {
10686
+ holdTimer = void 0;
10687
+ dirty = true;
10688
+ options.changed([], lastZoom);
10689
+ },
10690
+ Math.max(0, earliest - now) + 1
10691
+ );
10692
+ }
10693
+ function settledAround(tile) {
10694
+ if (disposed) return true;
10695
+ const now = performance.now();
10696
+ for (const placement3 of lastPlacements ?? []) {
10697
+ const [px, py] = cameraTileAt(placement3.at[0], placement3.at[1], tile.z);
10698
+ if (Math.abs(Math.floor(px) - tile.x) > reachTiles) continue;
10699
+ if (Math.abs(Math.floor(py) - tile.y) > reachTiles) continue;
10700
+ const key = keyOf(placement3);
10701
+ if (!loaded.has(key) && (failed.get(key) ?? 0) <= now) return false;
10702
+ }
10703
+ const url = lastLandmarks;
10704
+ if (!url) return true;
10705
+ if (!tileset || tileset.url !== url)
10706
+ return tilesetFailed?.url === url && tilesetFailed.until > now;
10707
+ const scale = 2 ** (tileset.tileZoom - tile.z);
10708
+ const left = Math.floor(tile.x * scale) - 1;
10709
+ const right = Math.ceil((tile.x + 1) * scale);
10710
+ const top = Math.floor(tile.y * scale) - 1;
10711
+ const bottom = Math.ceil((tile.y + 1) * scale);
10712
+ for (const x3 of [left, right])
10713
+ for (const y2 of [top, bottom]) {
10714
+ const [rx, ry] = tileflowLandmarkRegionOf([x3, y2]);
10715
+ const regionKey = `${rx}/${ry}`;
10716
+ if (!tileset.regions.has(regionKey) || regions.has(regionKey)) continue;
10717
+ if ((regionFailed.get(regionKey) ?? 0) > now) continue;
10718
+ return false;
10719
+ }
10720
+ return true;
10721
+ }
9769
10722
  return {
9770
- update(placements, landmarks, tiles, zoom) {
10723
+ update(placements, landmarks, tiles, zoom, shown = true) {
9771
10724
  if (disposed) return;
9772
10725
  lastZoom = zoom;
10726
+ lastLandmarks = landmarks;
9773
10727
  const near = /* @__PURE__ */ new Set();
9774
10728
  const now = performance.now();
9775
10729
  if (placements !== lastPlacements) {
@@ -9786,6 +10740,7 @@ function createTileflowPlacedModels(options) {
9786
10740
  bottom = Math.max(bottom, tile.y);
9787
10741
  }
9788
10742
  for (const placement3 of placements) {
10743
+ if (readTileflowTeamModelName(placement3.url)) continue;
9789
10744
  const [x3, y2] = cameraTileAt(placement3.at[0], placement3.at[1], zoom);
9790
10745
  if (x3 < left - reachTiles || x3 >= right + 1 + reachTiles || y2 < top - reachTiles || y2 >= bottom + 1 + reachTiles)
9791
10746
  continue;
@@ -9832,6 +10787,7 @@ function createTileflowPlacedModels(options) {
9832
10787
  loadTileset(landmarks);
9833
10788
  const nearRegions = /* @__PURE__ */ new Set();
9834
10789
  const nearHashes = /* @__PURE__ */ new Set();
10790
+ const onTheirWay = /* @__PURE__ */ new Map();
9835
10791
  for (const [tileKey, [x3, y2]] of landmarkTilesNear(tiles, zoom)) {
9836
10792
  const [rx, ry] = tileflowLandmarkRegionOf([x3, y2]);
9837
10793
  const regionKey = `${rx}/${ry}`;
@@ -9844,12 +10800,17 @@ function createTileflowPlacedModels(options) {
9844
10800
  continue;
9845
10801
  }
9846
10802
  region.used = now;
9847
- const hash4 = region.tiles.get(tileKey);
9848
- if (!hash4) continue;
10803
+ const named = region.tiles.get(tileKey);
10804
+ if (!named) continue;
10805
+ const { hash: hash4 } = named;
9849
10806
  nearHashes.add(hash4);
9850
10807
  const tile = landmarkTiles.get(hash4);
9851
10808
  if (tile) tile.used = now;
9852
- else if (!tileLoading.has(hash4) && (tileFailed.get(hash4) ?? 0) <= now) loadTile(hash4);
10809
+ else {
10810
+ const failing = (tileFailed.get(hash4) ?? 0) > now;
10811
+ if (!tileLoading.has(hash4) && !failing) loadTile(hash4);
10812
+ if (named.landmarks && !failing) onTheirWay.set(hash4, named.landmarks);
10813
+ }
9853
10814
  }
9854
10815
  if (nearHashes.size !== wantedTiles.size || [...nearHashes].some((hash4) => !wantedTiles.has(hash4))) {
9855
10816
  wantedTiles = nearHashes;
@@ -9877,25 +10838,71 @@ function createTileflowPlacedModels(options) {
9877
10838
  landmarkTiles.delete(hash4);
9878
10839
  released.push(...tile.entries);
9879
10840
  }
9880
- if (!dirty) return;
10841
+ recheck();
10842
+ const next = dirty ? standing() : active;
10843
+ const holdNext = holdingNow(
10844
+ onTheirWay,
10845
+ next.filter((entry) => !entry.landmark),
10846
+ now,
10847
+ shown
10848
+ );
10849
+ const sameHold = holdNext.length === held.length && holdNext.every((entry, at) => entry.key === held[at].key);
10850
+ if (!dirty && sameHold) return;
9881
10851
  dirty = false;
9882
- const next = standing();
9883
10852
  const before = new Map(active.map((entry) => [entry, true]));
9884
10853
  const changed = [];
9885
- for (const entry of next) if (!before.delete(entry)) changed.push(...tilesOf(entry, zoom));
9886
- for (const entry of before.keys()) changed.push(...tilesOf(entry, zoom));
10854
+ const reach = (entry) => tilesOf({ placement: entry.placement, bounds: entry.model.bounds }, zoom);
10855
+ for (const entry of next)
10856
+ if (!before.delete(entry)) {
10857
+ entry.standingSince = now;
10858
+ changed.push(...reach(entry));
10859
+ }
10860
+ for (const entry of before.keys()) {
10861
+ entry.standingSince = void 0;
10862
+ changed.push(...reach(entry));
10863
+ }
10864
+ const heldBefore = new Map(held.map((entry) => [entry.key, entry]));
10865
+ for (const entry of holdNext)
10866
+ if (!heldBefore.delete(entry.key)) changed.push(...tilesOf(entry, zoom));
10867
+ for (const entry of heldBefore.values()) changed.push(...tilesOf(entry, zoom));
9887
10868
  active = next;
10869
+ held = holdNext;
10870
+ scheduleHoldEnd(now);
9888
10871
  if (changed.length > 0) {
9889
10872
  version2 += 1;
9890
10873
  options.changed(changed, zoom);
9891
10874
  }
9892
10875
  },
10876
+ settle(tile, signal) {
10877
+ if (settledAround(tile)) return Promise.resolve();
10878
+ return new Promise((resolve) => {
10879
+ const waiter = {
10880
+ settled: () => signal.aborted || settledAround(tile),
10881
+ resolve: () => {
10882
+ clearTimeout(timer);
10883
+ signal.removeEventListener("abort", done);
10884
+ resolve();
10885
+ }
10886
+ };
10887
+ const done = () => {
10888
+ waiting.delete(waiter);
10889
+ waiter.resolve();
10890
+ };
10891
+ const timer = setTimeout(done, settleFor);
10892
+ signal.addEventListener("abort", done, { once: true });
10893
+ waiting.add(waiter);
10894
+ });
10895
+ },
9893
10896
  masksIn(tile, extent5) {
9894
10897
  let masks;
9895
- for (const entry of active) {
9896
- if (entry.model.footprint.length === 0) continue;
10898
+ const footprints = [
10899
+ ...active.map((entry) => ({ placement: entry.placement, footprint: entry.model.footprint })),
10900
+ ...held
10901
+ ];
10902
+ for (const entry of footprints) {
10903
+ if (entry.footprint.length === 0) continue;
9897
10904
  const rings = tileflowModelFootprintInTile(
9898
- entry.model.footprint,
10905
+ entry.footprint,
9899
10906
  entry.placement,
9900
10907
  { x: tile.x, y: tile.y, zoom: tile.z },
9901
10908
  extent5
@@ -9916,10 +10923,13 @@ function createTileflowPlacedModels(options) {
9916
10923
  },
9917
10924
  shown: () => active,
9918
10925
  upload(context) {
9919
- for (const entry of released.splice(0)) releaseBuffers(context, entry);
10926
+ for (const entry of released.splice(0)) {
10927
+ releaseBuffers(context, entry);
10928
+ closeModelImages(entry.images);
10929
+ }
9920
10930
  for (const entry of active) {
9921
10931
  if (entry.gl) continue;
9922
- entry.gl = entry.model.levels.map((level) => {
10932
+ entry.gl = entry.model.levels.map((level, levelIndex) => {
9923
10933
  const vao = context.createVertexArray();
9924
10934
  context.bindVertexArray(vao);
9925
10935
  const vbo = context.createBuffer();
@@ -9942,11 +10952,13 @@ function createTileflowPlacedModels(options) {
9942
10952
  const ibo = context.createBuffer();
9943
10953
  context.bindBuffer(context.ELEMENT_ARRAY_BUFFER, ibo);
9944
10954
  context.bufferData(context.ELEMENT_ARRAY_BUFFER, level.indices, context.STATIC_DRAW);
10955
+ const surface = uploadModelSurface(context, level, entry.images?.[levelIndex] ?? []);
9945
10956
  context.bindVertexArray(null);
9946
10957
  return {
9947
10958
  vao,
9948
10959
  vbo,
9949
10960
  ibo,
10961
+ ...surface ? { surface } : {},
9950
10962
  type: level.indices instanceof Uint32Array ? context.UNSIGNED_INT : context.UNSIGNED_SHORT
9951
10963
  };
9952
10964
  });
@@ -9963,16 +10975,25 @@ function createTileflowPlacedModels(options) {
9963
10975
  },
9964
10976
  key: () => String(version2),
9965
10977
  release(context) {
9966
- const all = [...loaded.values(), ...released.splice(0)];
10978
+ const retired = released.splice(0);
10979
+ const all = [...loaded.values(), ...retired];
9967
10980
  for (const tile of landmarkTiles.values()) all.push(...tile.entries);
9968
10981
  for (const entry of all) releaseBuffers(context, entry);
10982
+ for (const entry of retired) closeModelImages(entry.images);
9969
10983
  },
9970
10984
  dispose() {
9971
10985
  disposed = true;
10986
+ if (holdTimer !== void 0) clearTimeout(holdTimer);
10987
+ for (const waiter of waiting) waiter.resolve();
10988
+ waiting.clear();
10989
+ held = [];
9972
10990
  for (const controller of loading.values()) controller.abort();
9973
10991
  for (const controller of [...tileLoading.values(), ...regionLoading.values()])
9974
10992
  controller.abort();
9975
10993
  tilesetLoad?.controller.abort();
10994
+ for (const entry of [...loaded.values(), ...released]) closeModelImages(entry.images);
10995
+ for (const tile of landmarkTiles.values())
10996
+ for (const entry of tile.entries) closeModelImages(entry.images);
9976
10997
  loading.clear();
9977
10998
  tileLoading.clear();
9978
10999
  regionLoading.clear();
@@ -9984,27 +11005,64 @@ function createTileflowPlacedModels(options) {
9984
11005
  };
9985
11006
  }
9986
11007
  function footprintsMeet(landmark, model) {
9987
- if (landmark.model.footprint.length === 0 || model.model.footprint.length === 0) return false;
11008
+ const footprint = "model" in landmark ? landmark.model.footprint : landmark.footprint;
11009
+ if (footprint.length === 0 || model.model.footprint.length === 0) return false;
9988
11010
  const [x3, y2] = cameraTileAt(landmark.placement.at[0], landmark.placement.at[1], 14);
9989
11011
  const tile = { x: Math.floor(x3), y: Math.floor(y2), zoom: 14 };
9990
- const ours = tileflowModelFootprintInTile(
9991
- landmark.model.footprint,
9992
- landmark.placement,
9993
- tile,
9994
- 8192
9995
- );
11012
+ const ours = tileflowModelFootprintInTile(footprint, landmark.placement, tile, 8192);
9996
11013
  const theirs = tileflowModelFootprintInTile(model.model.footprint, model.placement, tile, 8192);
9997
11014
  return tileflowRingsOverlap(ours, theirs, 0);
9998
11015
  }
11016
+ function boundsOf(footprint) {
11017
+ let [minX, minY, maxX, maxY] = [0, 0, 0, 0];
11018
+ for (const ring of footprint)
11019
+ for (let at = 0; at + 1 < ring.length; at += 2) {
11020
+ minX = Math.min(minX, ring[at]);
11021
+ maxX = Math.max(maxX, ring[at]);
11022
+ minY = Math.min(minY, ring[at + 1]);
11023
+ maxY = Math.max(maxY, ring[at + 1]);
11024
+ }
11025
+ return [minX, minY, 0, maxX, maxY, 0];
11026
+ }
9999
11027
  function releaseBuffers(context, entry) {
10000
11028
  for (const level of entry.gl ?? []) {
10001
11029
  context.deleteVertexArray(level.vao);
10002
11030
  context.deleteBuffer(level.vbo);
10003
11031
  context.deleteBuffer(level.ibo);
11032
+ if (level.surface) {
11033
+ context.deleteBuffer(level.surface.vbo);
11034
+ for (const texture of level.surface.textures) context.deleteTexture(texture);
11035
+ }
10004
11036
  }
10005
11037
  entry.gl = void 0;
10006
11038
  }
10007
11039
 
11040
+ // src/models-3d-role-lighting.ts
11041
+ init_theme_blend();
11042
+ function sampleModelRole(profile, identity) {
11043
+ let seed = 2166136261;
11044
+ for (let i2 = 0; i2 < identity.length; i2++)
11045
+ seed = Math.imul(seed ^ identity.charCodeAt(i2), 16777619);
11046
+ const random = (channel) => {
11047
+ let value = Math.imul(seed ^ channel, 73244475);
11048
+ value = Math.imul(value ^ value >>> 16, 73244475);
11049
+ return ((value ^ value >>> 16) >>> 0) / 4294967296;
11050
+ };
11051
+ const variation = profile?.colorVariation;
11052
+ let color = profile?.color ? tileflowColourChannels(profile.color)?.slice(0, 3) : void 0;
11053
+ if (variation) {
11054
+ const hsl = [variation.hue, variation.saturation, variation.lightness].map(
11055
+ ([low, high], channel) => low + (high - low) * random(channel + 1)
11056
+ );
11057
+ color = tileflowColourChannels(`hsl(${hsl[0]}, ${hsl[1]}%, ${hsl[2]}%)`).slice(0, 3);
11058
+ if (variation.space === "linear") color = color.map((c) => c ** (1 / 2.2));
11059
+ }
11060
+ return {
11061
+ strength: (profile?.emissiveStrength ?? 0) + (profile?.emissiveVariation ?? 0) * random(0),
11062
+ color
11063
+ };
11064
+ }
11065
+
10008
11066
  // src/scene-3d-browser.ts
10009
11067
  var tileflowSceneExtent = 8192;
10010
11068
  var tileflowSceneShadowFloor = 15;
@@ -10390,6 +11448,7 @@ var defaultTransition = 0;
10390
11448
  var flood = { reach: 10, colour: [1, 0.7, 0.4] };
10391
11449
  var edgeLine = { width: 1, day: 0.5 };
10392
11450
  var paletteWidth = 256;
11451
+ var modelFade = 500;
10393
11452
  var streetLightsLayerId = "tileflow-street-lights";
10394
11453
  var derivedParts = {
10395
11454
  surface: (rgb) => [...rgb],
@@ -10419,6 +11478,7 @@ function createTileflowBuildings3D(options) {
10419
11478
  let linking;
10420
11479
  let ground;
10421
11480
  const contactMask = createBuildingContactMask();
11481
+ const floodMask = createBuildingContactMask();
10422
11482
  let disposed = false;
10423
11483
  const scene = tileflowScene3D(map);
10424
11484
  let generation = 0;
@@ -10457,6 +11517,8 @@ function createTileflowBuildings3D(options) {
10457
11517
  context.UNSIGNED_BYTE,
10458
11518
  new Uint8Array([216, 208, 192, 255])
10459
11519
  );
11520
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MIN_FILTER, context.NEAREST);
11521
+ context.texParameteri(context.TEXTURE_2D, context.TEXTURE_MAG_FILTER, context.NEAREST);
10460
11522
  },
10461
11523
  onRemove(_2, context) {
10462
11524
  if (!isWebGL2(context)) return;
@@ -10473,6 +11535,7 @@ function createTileflowBuildings3D(options) {
10473
11535
  context.deleteBuffer(ground.vbo);
10474
11536
  }
10475
11537
  contactMask.release(context);
11538
+ floodMask.release(context);
10476
11539
  if (blank) context.deleteTexture(blank);
10477
11540
  blank = void 0;
10478
11541
  programs = {};
@@ -10485,7 +11548,13 @@ function createTileflowBuildings3D(options) {
10485
11548
  if (!isWebGL2(context)) return;
10486
11549
  ensureCamera();
10487
11550
  if (target > 0 || scale > 0)
10488
- models.update(design().models ?? [], design().landmarks?.url, camera.ready(), camera.zoom);
11551
+ models.update(
11552
+ design().models ?? [],
11553
+ design().landmarks?.url,
11554
+ camera.coverage(),
11555
+ camera.zoom,
11556
+ visibleScale() > 0
11557
+ );
10489
11558
  if (!programs.depth) return;
10490
11559
  if (!design().shadows || visibleScale() <= 0) {
10491
11560
  if (registered) unregister();
@@ -10511,6 +11580,7 @@ function createTileflowBuildings3D(options) {
10511
11580
  if (target > 0 || scale > 0) upload(context);
10512
11581
  if (visibleScale() <= 0) {
10513
11582
  contactMask.release(context);
11583
+ floodMask.release(context);
10514
11584
  return;
10515
11585
  }
10516
11586
  draw(context, args);
@@ -10529,6 +11599,8 @@ function createTileflowBuildings3D(options) {
10529
11599
  const decoded = tile.layers[layers.building];
10530
11600
  if (!decoded) return emptyTile;
10531
11601
  const places = layers.places ? tile.layers[layers.places] : void 0;
11602
+ await models.settle(tile, signal);
11603
+ signal.throwIfAborted();
10532
11604
  const masks = models.masksIn(tile, tileflowBuildingExtent);
10533
11605
  const mesh = await mesher.build(
10534
11606
  decoded,
@@ -10868,13 +11940,18 @@ function createTileflowBuildings3D(options) {
10868
11940
  modelDepth.uniforms.u_offset,
10869
11941
  ...frame.offsetOf({ x: stand.tileX, y: stand.tileY }, camera.zoom)
10870
11942
  );
10871
- drawModel(context, modelDepth, model, models.levelOf(model, centre));
11943
+ drawModel(context, modelDepth, model, models.levelOf(model, centre), "depth");
10872
11944
  }
10873
11945
  }
10874
11946
  };
10875
11947
  function modelsDrawn() {
10876
11948
  return models.shown().filter((model) => model.gl);
10877
11949
  }
11950
+ function appearanceOf(model, now) {
11951
+ if (model.standingSince === void 0) return 1;
11952
+ const reduced = typeof matchMedia === "function" && matchMedia("(prefers-reduced-motion: reduce)").matches;
11953
+ return reduced ? 1 : Math.min(1, Math.max(0, (now - model.standingSince) / modelFade));
11954
+ }
10878
11955
  function standOf(model) {
10879
11956
  const [longitude, latitude] = model.placement.at;
10880
11957
  const [x3, y2] = cameraTileAt(longitude, latitude, camera.zoom);
@@ -10899,13 +11976,55 @@ function createTileflowBuildings3D(options) {
10899
11976
  context.uniform2f(u.u_anchor, stand.anchor[0], stand.anchor[1]);
10900
11977
  context.uniform1f(u.u_model_metres, stand.metres);
10901
11978
  }
10902
- function drawModel(context, program, model, level) {
11979
+ function drawModel(context, program, model, level, pass = "solid") {
10903
11980
  const buffers = model.gl[level];
10904
11981
  const mesh = model.model.levels[level];
10905
11982
  context.uniform3f(program.uniforms.u_level_centre, ...mesh.centre);
10906
11983
  context.uniform1f(program.uniforms.u_level_step, mesh.step);
11984
+ context.uniform2f(
11985
+ program.uniforms.u_model_height ?? null,
11986
+ model.model.bounds[2],
11987
+ model.model.bounds[5] - model.model.bounds[2]
11988
+ );
10907
11989
  context.bindVertexArray(buffers.vao);
10908
- context.drawElements(context.TRIANGLES, mesh.indices.length, buffers.type, 0);
11990
+ if (!mesh.surface) {
11991
+ if (pass === "blend") return;
11992
+ bindModelMaterial(context, program.uniforms, void 0, void 0, blank);
11993
+ context.drawElements(context.TRIANGLES, mesh.indices.length, buffers.type, 0);
11994
+ return;
11995
+ }
11996
+ const ranges = mesh.surface.ranges.map((range, index) => ({ range, index })).filter(({ range }) => {
11997
+ const transparent2 = mesh.surface.materials[range.material].alphaMode === "BLEND";
11998
+ return pass === "depth" || transparent2 === (pass === "blend");
11999
+ });
12000
+ if (pass === "blend" && ranges.length > 1) {
12001
+ const camera2 = modelCamera(model), turn = model.placement.heading * Math.PI / 180;
12002
+ const distance = (index) => {
12003
+ const c = buffers.surface.centres.subarray(index * 3, index * 3 + 3);
12004
+ const x3 = c[0] * Math.cos(turn) + c[1] * Math.sin(turn);
12005
+ const y2 = -c[0] * Math.sin(turn) + c[1] * Math.cos(turn);
12006
+ return (x3 - camera2[0]) ** 2 + (y2 - camera2[1]) ** 2 + (c[2] - camera2[2]) ** 2;
12007
+ };
12008
+ ranges.sort((a, b2) => distance(b2.index) - distance(a.index));
12009
+ }
12010
+ for (const { range } of ranges) {
12011
+ const [low, high] = range.emissiveBounds ?? [model.model.bounds[2], model.model.bounds[5]];
12012
+ context.uniform2f(program.uniforms.u_model_height ?? null, low, high - low);
12013
+ bindModelMaterial(
12014
+ context,
12015
+ program.uniforms,
12016
+ mesh.surface.materials[range.material],
12017
+ buffers.surface,
12018
+ blank
12019
+ );
12020
+ context.drawElements(
12021
+ context.TRIANGLES,
12022
+ range.count,
12023
+ buffers.type,
12024
+ range.from * mesh.indices.BYTES_PER_ELEMENT
12025
+ );
12026
+ }
12027
+ context.disable(context.CULL_FACE);
10909
12028
  }
10910
12029
  function partColoursNow() {
10911
12030
  const { themes, lower, upper, mix: mix2 } = designsOnScreen();
@@ -10920,6 +12039,94 @@ function createTileflowBuildings3D(options) {
10920
12039
  }
10921
12040
  return out;
10922
12041
  }
12042
+ function partMixNow() {
12043
+ const { themes, lower, upper, mix: mix2 } = designsOnScreen();
12044
+ return Float32Array.from(
12045
+ Object.keys(tileflowModelParts).map(
12046
+ (part) => (themes[lower].modelColors?.[part] ? 1 - mix2 : 0) + (themes[upper].modelColors?.[part] ? mix2 : 0)
12047
+ )
12048
+ );
12049
+ }
12050
+ function modelCamera(model) {
12051
+ const zoom = map.getZoom();
12052
+ const [cx, cy] = cameraTileAt(map.getCenter().lng, map.getCenter().lat, zoom);
12053
+ const [mx, my] = cameraTileAt(...model.placement.at, zoom);
12054
+ const metres = 40075016686e-3 * Math.cos(model.placement.at[1] * Math.PI / 180) / 2 ** zoom;
12055
+ const distance = (map.getCanvas?.().clientHeight ?? 720) * metres / 512 / (2 * Math.tan((map.getVerticalFieldOfView?.() ?? 36.87) * Math.PI / 360));
12056
+ const pitch = map.getPitch() * Math.PI / 180, bearing = map.getBearing() * Math.PI / 180;
12057
+ return [
12058
+ (cx - mx) * metres - distance * Math.sin(pitch) * Math.sin(bearing),
12059
+ (my - cy) * metres - distance * Math.sin(pitch) * Math.cos(bearing),
12060
+ distance * Math.cos(pitch)
12061
+ ];
12062
+ }
12063
+ const modelLightingCache = /* @__PURE__ */ new Map();
12064
+ let modelLightingThemes;
12065
+ let modelLightingPosition = "";
12066
+ function modelLightingNow(identity) {
12067
+ const { themes, lower, upper, mix: mix2 } = designsOnScreen();
12068
+ const position = `${lower}:${upper}:${mix2}`;
12069
+ if (themes !== modelLightingThemes || position !== modelLightingPosition) {
12070
+ modelLightingCache.clear();
12071
+ modelLightingThemes = themes;
12072
+ modelLightingPosition = position;
12073
+ }
12074
+ const cached = modelLightingCache.get(identity);
12075
+ if (cached) return cached;
12076
+ const strengths = new Float32Array(7);
12077
+ const colours = new Float32Array(28);
12078
+ const gradients = new Float32Array(28);
12079
+ const powers = new Float32Array(7);
12080
+ const materialColours = new Float32Array(28);
12081
+ const materials = new Float32Array(28);
12082
+ for (const [role, id] of Object.entries(tileflowModelLightingRoles)) {
12083
+ const from = themes[lower].modelLighting?.[role], to = themes[upper].modelLighting?.[role];
12084
+ const sampledFrom = sampleModelRole(from, `${identity}:${role}`);
12085
+ const sampledTo = sampleModelRole(to, `${identity}:${role}`);
12086
+ strengths[id] = sampledFrom.strength * (1 - mix2) + sampledTo.strength * mix2;
12087
+ const ca = sampledFrom.color, cb = sampledTo.color;
12088
+ for (let channel = 0; channel < 3; channel++)
12089
+ materialColours[id * 4 + channel] = (ca?.[channel] ?? cb?.[channel] ?? 0) * (1 - mix2) + (cb?.[channel] ?? ca?.[channel] ?? 0) * mix2;
12090
+ materialColours[id * 4 + 3] = (ca ? (from?.colorMix ?? 1) * (1 - mix2) : 0) + (cb ? (to?.colorMix ?? 1) * mix2 : 0);
12091
+ for (const [channel, key] of ["metallic", "roughness"].entries()) {
12092
+ materials[id * 4 + channel] = (from?.[key] ?? to?.[key] ?? 0) * (1 - mix2) + (to?.[key] ?? from?.[key] ?? 0) * mix2;
12093
+ materials[id * 4 + 2 + channel] = (from?.[key] === void 0 ? 0 : 1 - mix2) + (to?.[key] === void 0 ? 0 : mix2);
12094
+ }
12095
+ const a = rgbOf2(from?.emissiveColor), b2 = rgbOf2(to?.emissiveColor);
12096
+ for (let channel = 0; channel < 3; channel += 1)
12097
+ colours[id * 4 + channel] = (a?.[channel] ?? b2?.[channel] ?? 0) * (1 - mix2) + (b2?.[channel] ?? a?.[channel] ?? 0) * mix2;
12098
+ colours[id * 4 + 3] = (a ? 1 - mix2 : 0) + (b2 ? mix2 : 0);
12099
+ const first = from?.emissiveHeight ?? defaultModelEmissionGradient;
12100
+ const second = to?.emissiveHeight ?? defaultModelEmissionGradient;
12101
+ for (let component = 0; component < 4; component += 1)
12102
+ gradients[id * 4 + component] = first[component] * (1 - mix2) + second[component] * mix2;
12103
+ powers[id] = first[4] * (1 - mix2) + second[4] * mix2;
12104
+ }
12105
+ const result = { strengths, colours, gradients, powers, materialColours, materials };
12106
+ if (modelLightingCache.size >= 1024)
12107
+ modelLightingCache.delete(modelLightingCache.keys().next().value);
12108
+ modelLightingCache.set(identity, result);
12109
+ return result;
12110
+ }
12111
+ function floodLightNow() {
12112
+ const { themes, lower, upper, mix: mix2 } = designsOnScreen();
12113
+ const first = themes[lower].floodLight ?? defaultBuildingFloodLight;
12114
+ const second = themes[upper].floodLight ?? defaultBuildingFloodLight;
12115
+ const number = (a2, b3) => a2 * (1 - mix2) + b3 * mix2;
12116
+ const a = rgbOf2(first.color) ?? rgbOf2(defaultBuildingFloodLight.color);
12117
+ const b2 = rgbOf2(second.color) ?? rgbOf2(defaultBuildingFloodLight.color);
12118
+ return {
12119
+ intensity: number(first.intensity, second.intensity) * buildingFloodLightShare(sunNow(), number(first.brightnessScale, second.brightnessScale)),
12120
+ colour: a.map((value, i2) => number(value, b2[i2])),
12121
+ radius: [
12122
+ number(first.groundRadius[0], second.groundRadius[0]),
12123
+ number(first.groundRadius[1], second.groundRadius[1])
12124
+ ],
12125
+ minGroundHeight: number(first.minGroundHeight, second.minGroundHeight),
12126
+ minWallHeight: number(first.minWallHeight, second.minWallHeight),
12127
+ wallHeightRatio: number(first.wallHeightRatio, second.wallHeightRatio)
12128
+ };
12129
+ }
10923
12130
  function placed2(x3, y2) {
10924
12131
  const frame = scene.frame();
10925
12132
  return frame && frame.zoom === camera.zoom ? { offset: frame.offsetOf({ x: x3, y: y2 }, camera.zoom), metres: frame.metres } : { offset: camera.offsetOf({ x: x3, y: y2 }), metres: camera.metres };
@@ -10986,8 +12193,12 @@ function createTileflowBuildings3D(options) {
10986
12193
  const shown = visibleScale();
10987
12194
  const themes = colouredByData() ? designsOnScreen() : void 0;
10988
12195
  const nightLight2 = mixed2((entry) => entry.nightLight);
12196
+ const architectural = floodLightNow();
10989
12197
  const edges = mixed2((entry) => entry.edges);
10990
12198
  const { occlusion: occlusionOn, windows: windowsOn } = design();
12199
+ const now = performance.now();
12200
+ const appearing = standing.filter((entry) => appearanceOf(entry, now) < 1);
12201
+ const steady = standing.filter((entry) => !appearing.includes(entry));
10991
12202
  context.enable(context.DEPTH_TEST);
10992
12203
  context.depthFunc(context.LEQUAL);
10993
12204
  context.depthMask(true);
@@ -11010,6 +12221,13 @@ function createTileflowBuildings3D(options) {
11010
12221
  context.uniform1f(u.u_windows, windowsOn ? 1 : 0);
11011
12222
  context.uniform1f(u.u_flood, light.night * nightLight2);
11012
12223
  context.uniform3fv(u.u_flood_colour, flood.colour);
12224
+ context.uniform1f(u.u_architectural_intensity, architectural.intensity);
12225
+ context.uniform3fv(u.u_architectural_colour, architectural.colour);
12226
+ context.uniform2f(
12227
+ u.u_architectural_wall,
12228
+ architectural.minWallHeight,
12229
+ architectural.wallHeightRatio
12230
+ );
11013
12231
  context.uniform1f(
11014
12232
  u.u_variation,
11015
12233
  mixed2((entry) => entry.colorVariation)
@@ -11062,8 +12280,34 @@ function createTileflowBuildings3D(options) {
11062
12280
  range.from * 4
11063
12281
  );
11064
12282
  }
12283
+ drawModels(steady);
12284
+ });
12285
+ for (const entry of appearing)
12286
+ drawBuildingOpacity(
12287
+ context,
12288
+ visibleOpacity() * appearanceOf(entry, now),
12289
+ () => drawModels([entry])
12290
+ );
12291
+ if (appearing.length > 0) map.triggerRepaint();
12292
+ context.enable(context.BLEND);
12293
+ context.blendFuncSeparate(
12294
+ context.SRC_ALPHA,
12295
+ context.ONE_MINUS_SRC_ALPHA,
12296
+ context.ZERO,
12297
+ context.ONE
12298
+ );
12299
+ context.depthMask(false);
12300
+ context.depthFunc(context.LEQUAL);
12301
+ drawModels(
12302
+ [...standing].sort((a, b2) => {
12303
+ const distance = (entry) => Math.hypot(...modelCamera(entry));
12304
+ return distance(b2) - distance(a);
12305
+ }),
12306
+ "blend"
12307
+ );
12308
+ function drawModels(list, pass = "solid") {
11065
12309
  const model = programs.model;
11066
- if (model && standing.length > 0) {
12310
+ if (model && list.length > 0) {
11067
12311
  const m = model.uniforms;
11068
12312
  context.useProgram(model.program);
11069
12313
  context.disable(context.CULL_FACE);
@@ -11073,20 +12317,37 @@ function createTileflowBuildings3D(options) {
11073
12317
  context.uniform3fv(m.u_sun_light, light.sunLight);
11074
12318
  context.uniform3fv(m.u_sky_light, light.skyLight);
11075
12319
  context.uniform1f(m.u_night, light.night);
12320
+ context.uniform1f(
12321
+ m.u_ambient_occlusion,
12322
+ occlusionOn ? mixed2((entry) => entry.modelOcclusion ?? 0.75) : 0
12323
+ );
11076
12324
  context.uniform3fv(m.u_part_colours, partColoursNow());
12325
+ context.uniform1fv(m.u_part_mix, partMixNow());
11077
12326
  scene.bindShadow(context, m, light.strength);
11078
12327
  const centre = map.getCenter();
11079
- for (const entry of standing) {
12328
+ for (const entry of list) {
12329
+ const emission = modelLightingNow(entry.model.id);
12330
+ context.uniform1fv(m.u_role_emission, emission.strengths);
12331
+ context.uniform4fv(m.u_role_emission_colour, emission.colours);
12332
+ context.uniform4fv(m.u_role_emission_gradient, emission.gradients);
12333
+ context.uniform1fv(m.u_role_emission_power, emission.powers);
12334
+ context.uniform4fv(m.u_role_colour, emission.materialColours);
12335
+ context.uniform4fv(m.u_role_material, emission.materials);
11080
12336
  const stand = standOf(entry);
11081
12337
  project(context, model, args, stand.tileX, stand.tileY);
11082
12338
  const at = placed2(stand.tileX, stand.tileY);
11083
12339
  context.uniform2f(m.u_offset, ...at.offset);
11084
12340
  context.uniform1f(m.u_metres, at.metres);
11085
12341
  setPlacement(context, model, entry, stand);
11086
- drawModel(context, model, entry, models.levelOf(entry, centre));
12342
+ context.uniform3fv(m.u_view_position, modelCamera(entry));
12343
+ context.uniform1f(
12344
+ m.u_opacity,
12345
+ pass === "blend" ? visibleOpacity() * appearanceOf(entry, now) : 1
12346
+ );
12347
+ drawModel(context, model, entry, models.levelOf(entry, centre), pass);
11087
12348
  }
11088
12349
  }
11089
- });
12350
+ }
11090
12351
  context.depthMask(false);
11091
12352
  context.enable(context.BLEND);
11092
12353
  context.blendFuncSeparate(context.DST_COLOR, context.ZERO, context.ZERO, context.ONE);
@@ -11114,7 +12375,7 @@ function createTileflowBuildings3D(options) {
11114
12375
  context.drawArrays(context.TRIANGLE_STRIP, 0, 4);
11115
12376
  }
11116
12377
  }
11117
- const walls = (program) => {
12378
+ const walls = (program, reach = flood.reach) => {
11118
12379
  for (const tile of entries) {
11119
12380
  const { x: x3, y: y2, value } = tile;
11120
12381
  const mesh = value.mesh;
@@ -11122,7 +12383,7 @@ function createTileflowBuildings3D(options) {
11122
12383
  project(context, program, args, x3, y2);
11123
12384
  context.uniform2f(program.uniforms.u_offset, ...camera.offsetOf({ x: x3, y: y2 }));
11124
12385
  context.bindVertexArray(value.gl.vao);
11125
- for (const range of viewRanges(args, tile, flood.reach, true))
12386
+ for (const range of viewRanges(args, tile, reach, true))
11126
12387
  context.drawElements(
11127
12388
  context.TRIANGLES,
11128
12389
  range.count,
@@ -11185,6 +12446,95 @@ function createTileflowBuildings3D(options) {
11185
12446
  context.uniform3fv(program.uniforms.u_flood_colour, flood.colour);
11186
12447
  walls(program);
11187
12448
  }
12449
+ if (architectural.intensity > 0 && entries.length > 0 && ground) {
12450
+ const program = programs.architecturalFlood;
12451
+ const texture = floodMask.render(context, () => {
12452
+ context.useProgram(program.program);
12453
+ context.uniform1f(program.uniforms.u_scale, shown);
12454
+ context.uniform1f(program.uniforms.u_metres, camera.metres);
12455
+ context.uniform2f(
12456
+ program.uniforms.u_radius,
12457
+ architectural.radius[0],
12458
+ architectural.radius[1]
12459
+ );
12460
+ context.uniform1f(program.uniforms.u_min_height, architectural.minGroundHeight);
12461
+ context.uniform1f(
12462
+ program.uniforms.u_intensity,
12463
+ architectural.intensity * visibleOpacity()
12464
+ );
12465
+ walls(program, architectural.radius[1]);
12466
+ });
12467
+ const composite = programs.architecturalFloodComposite;
12468
+ context.useProgram(composite.program);
12469
+ context.activeTexture(context.TEXTURE2);
12470
+ context.bindTexture(context.TEXTURE_2D, texture);
12471
+ context.uniform1i(composite.uniforms.u_mask, 2);
12472
+ context.uniform3fv(composite.uniforms.u_colour, architectural.colour);
12473
+ context.blendFuncSeparate(
12474
+ context.SRC_ALPHA,
12475
+ context.ONE_MINUS_SRC_ALPHA,
12476
+ context.ZERO,
12477
+ context.ONE
12478
+ );
12479
+ context.bindVertexArray(ground.vao);
12480
+ const xs = entries.map(({ x: x3 }) => x3), ys = entries.map(({ y: y2 }) => y2);
12481
+ for (let x3 = Math.min(...xs) - 1; x3 <= Math.max(...xs) + 1; x3 += 1)
12482
+ for (let y2 = Math.min(...ys) - 1; y2 <= Math.max(...ys) + 1; y2 += 1) {
12483
+ if (!groundVisible(args, x3, y2)) continue;
12484
+ project(context, composite, args, x3, y2);
12485
+ context.drawArrays(context.TRIANGLE_STRIP, 0, 4);
12486
+ }
12487
+ context.bindTexture(context.TEXTURE_2D, null);
12488
+ } else {
12489
+ floodMask.release(context);
12490
+ }
12491
+ const emitterProgram = programs.modelEmitter;
12492
+ if (emitterProgram) {
12493
+ const u = emitterProgram.uniforms;
12494
+ context.useProgram(emitterProgram.program);
12495
+ context.disable(context.CULL_FACE);
12496
+ context.blendFuncSeparate(
12497
+ context.SRC_ALPHA,
12498
+ context.ONE_MINUS_SRC_ALPHA,
12499
+ context.ZERO,
12500
+ context.ONE
12501
+ );
12502
+ context.bindVertexArray(null);
12503
+ context.uniform1f(u.u_scale, shown);
12504
+ const palette = partColoursNow();
12505
+ const paletteMix = partMixNow();
12506
+ for (const entry of standing) {
12507
+ const level = models.levelOf(entry, map.getCenter());
12508
+ const surface = entry.model.levels[level].surface;
12509
+ if (!surface?.emitters?.length) continue;
12510
+ const stand = standOf(entry), lighting2 = modelLightingNow(entry.model.id);
12511
+ project(context, emitterProgram, args, stand.tileX, stand.tileY);
12512
+ setPlacement(context, emitterProgram, entry, stand);
12513
+ context.uniform1f(u.u_opacity, visibleOpacity() * appearanceOf(entry, now));
12514
+ for (const [index, emitter] of surface.emitters.entries()) {
12515
+ const role = tileflowModelLightingRoles[emitter.role], part = tileflowModelParts[emitter.part];
12516
+ const geometry = entry.gl[level].surface.emitters[index];
12517
+ const gradient = lighting2.gradients.subarray(role * 4, role * 4 + 4);
12518
+ const bounds = entry.model.bounds;
12519
+ const height = (emitter.start[2] - bounds[2]) / Math.max(1e-3, bounds[5] - bounds[2]);
12520
+ const at = gradient[0] === gradient[1] ? 1 : Math.min(1, Math.max(0, (height - gradient[0]) / (gradient[1] - gradient[0])));
12521
+ const multiplier = gradient[0] === gradient[1] ? 1 : gradient[2] + (gradient[3] - gradient[2]) * at ** 10 ** lighting2.powers[role];
12522
+ const strength = lighting2.strengths[role] * multiplier;
12523
+ if (strength <= 0) continue;
12524
+ const own = lighting2.colours.subarray(role * 4, role * 4 + 4);
12525
+ const tint = lighting2.materialColours.subarray(role * 4, role * 4 + 4);
12526
+ const tintWeight = tint[3] * (1 - paletteMix[part]);
12527
+ const colour = [0, 1, 2].map(
12528
+ (channel) => own[3] > 0 ? own[channel] : palette[part * 3 + channel] * (1 - tintWeight) + tint[channel] * tintWeight
12529
+ );
12530
+ context.uniform3fv(u.u_emitter_positions, geometry.positions);
12531
+ context.uniform4fv(u.u_emitter_coordinates, geometry.coordinates);
12532
+ context.uniform3fv(u.u_emitter_colour, colour);
12533
+ context.uniform1f(u.u_emitter_strength, strength);
12534
+ context.drawArrays(context.TRIANGLES, 0, 12);
12535
+ }
12536
+ }
12537
+ }
11188
12538
  context.bindVertexArray(null);
11189
12539
  context.depthMask(true);
11190
12540
  }
@@ -11197,9 +12547,18 @@ ${shaderData.define}`;
11197
12547
  ground: [tileflowBuildingGroundVertex(projected), tileflowBuildingGroundFragment],
11198
12548
  contact: [buildingContactVertex(projected), buildingContactFragment],
11199
12549
  contactComposite: [tileflowBuildingGroundVertex(projected), buildingContactCompositeFragment],
11200
- flood: [tileflowBuildingBandVertex(projected), tileflowBuildingFloodFragment]
12550
+ flood: [tileflowBuildingBandVertex(projected), tileflowBuildingFloodFragment],
12551
+ architecturalFlood: [
12552
+ buildingArchitecturalFloodVertex(projected),
12553
+ buildingArchitecturalFloodFragment
12554
+ ],
12555
+ architecturalFloodComposite: [
12556
+ tileflowBuildingGroundVertex(projected),
12557
+ buildingArchitecturalFloodComposite
12558
+ ]
11201
12559
  };
11202
12560
  sources.model = [tileflowModelVertex(projected), tileflowModelFragment];
12561
+ sources.modelEmitter = [modelEmitterVertex(projected), modelEmitterFragment];
11203
12562
  if (!programs.depth)
11204
12563
  sources.depth = [tileflowBuildingDepthVertex, tileflowBuildingDepthFragment];
11205
12564
  if (!programs.modelDepth)
@@ -11216,7 +12575,7 @@ ${shaderData.define}`;
11216
12575
  const fragment = shader(context.FRAGMENT_SHADER, fragmentText);
11217
12576
  context.attachShader(program, vertex);
11218
12577
  context.attachShader(program, fragment);
11219
- const bound = name === "model" || name === "modelDepth" ? tileflowModelAttributes : tileflowBuildingAttributes;
12578
+ const bound = name === "model" || name === "modelDepth" ? [...tileflowModelAttributes, ...tileflowModelSurfaceAttributes] : tileflowBuildingAttributes;
11220
12579
  for (const [index, [attribute]] of bound.entries())
11221
12580
  context.bindAttribLocation(program, index, attribute);
11222
12581
  context.linkProgram(program);
@@ -15380,6 +16739,120 @@ function validated5(shot) {
15380
16739
  throw new TypeError("Shot easing is smooth or linear.");
15381
16740
  }
15382
16741
 
16742
+ // src/notices.ts
16743
+ var tileflowNoticesMetadataKey = "tileflow:notices";
16744
+ var tileflowNoticeLimits = Object.freeze({
16745
+ maxNotices: 8,
16746
+ maxSources: 16,
16747
+ maxTextLength: 120
16748
+ });
16749
+ var identifierPattern = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/u;
16750
+ function readTileflowNotices(metadata) {
16751
+ if (!isRecord11(metadata)) return [];
16752
+ const value = metadata[tileflowNoticesMetadataKey];
16753
+ if (!Array.isArray(value)) return [];
16754
+ const notices = [];
16755
+ const ids = /* @__PURE__ */ new Set();
16756
+ for (const entry of value.slice(0, tileflowNoticeLimits.maxNotices)) {
16757
+ const notice = readNotice(entry);
16758
+ if (!notice || ids.has(notice.id)) continue;
16759
+ ids.add(notice.id);
16760
+ notices.push(notice);
16761
+ }
16762
+ return Object.freeze(notices);
16763
+ }
16764
+ function readNotice(value) {
16765
+ if (!isRecord11(value)) return void 0;
16766
+ const { id, sources, text: text2 } = value;
16767
+ if (typeof id !== "string" || !identifierPattern.test(id)) return void 0;
16768
+ if (typeof text2 !== "string" || text2.trim() !== text2 || !text2 || text2.length > tileflowNoticeLimits.maxTextLength || /\p{Cc}/u.test(text2)) {
16769
+ return void 0;
16770
+ }
16771
+ if (!Array.isArray(sources) || sources.length > tileflowNoticeLimits.maxSources || !sources.every((source) => typeof source === "string" && identifierPattern.test(source))) {
16772
+ return void 0;
16773
+ }
16774
+ return Object.freeze({ id, sources: Object.freeze([...sources]), text: text2 });
16775
+ }
16776
+ function isRecord11(value) {
16777
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
16778
+ }
16779
+
16780
+ // src/notices-browser.ts
16781
+ var styleChangeDelayMs = 200;
16782
+ function attachTileflowNotices(map, options = {}) {
16783
+ const container = map.getContainer();
16784
+ const document2 = container.ownerDocument;
16785
+ const browser = document2.defaultView;
16786
+ const position = options.position ?? "bottom-right";
16787
+ const element = document2.createElement("div");
16788
+ element.className = "maplibregl-ctrl tileflow-notice";
16789
+ element.setAttribute("role", "note");
16790
+ Object.assign(element.style, {
16791
+ background: "rgba(255, 255, 255, 0.82)",
16792
+ borderRadius: "3px",
16793
+ color: "rgba(0, 0, 0, 0.78)",
16794
+ font: '600 11px/18px "Helvetica Neue", Arial, Helvetica, sans-serif',
16795
+ letterSpacing: "0.01em",
16796
+ padding: "0 6px",
16797
+ whiteSpace: "nowrap"
16798
+ });
16799
+ const control = {
16800
+ onAdd: () => element,
16801
+ onRemove: () => element.remove()
16802
+ };
16803
+ let added = false;
16804
+ let disposed = false;
16805
+ let pending;
16806
+ function sync() {
16807
+ pending = void 0;
16808
+ if (disposed) return;
16809
+ let metadata;
16810
+ try {
16811
+ metadata = map.getStyle()?.metadata;
16812
+ } catch {
16813
+ return;
16814
+ }
16815
+ const notices = readTileflowNotices(metadata);
16816
+ if (notices.length === 0) {
16817
+ if (added) map.removeControl(control);
16818
+ added = false;
16819
+ return;
16820
+ }
16821
+ element.textContent = notices.map(({ text: text2 }) => text2).join(" \xB7 ");
16822
+ element.dataset.tileflowNotice = notices.map(({ id }) => id).join(" ");
16823
+ if (!added) map.addControl(control, position);
16824
+ added = true;
16825
+ }
16826
+ function cancelPending() {
16827
+ if (pending !== void 0) browser?.clearTimeout(pending);
16828
+ pending = void 0;
16829
+ }
16830
+ function scheduleSync() {
16831
+ if (disposed || !browser) return;
16832
+ cancelPending();
16833
+ pending = browser.setTimeout(sync, styleChangeDelayMs);
16834
+ }
16835
+ function onStyleLoad() {
16836
+ cancelPending();
16837
+ sync();
16838
+ }
16839
+ function dispose() {
16840
+ if (disposed) return;
16841
+ disposed = true;
16842
+ cancelPending();
16843
+ map.off("style.load", onStyleLoad);
16844
+ map.off("styledata", scheduleSync);
16845
+ map.off("remove", dispose);
16846
+ if (added) map.removeControl(control);
16847
+ added = false;
16848
+ }
16849
+ map.on("style.load", onStyleLoad);
16850
+ map.on("styledata", scheduleSync);
16851
+ map.on("remove", dispose);
16852
+ sync();
16853
+ return dispose;
16854
+ }
16855
+
15383
16856
  // src/data/world-release-id-values.ts
15384
16857
  var tileflowWorldReleaseIdMinimumLength = 12;
15385
16858
  var tileflowWorldReleaseIdMaximumLength = 128;
@@ -17484,6 +18957,7 @@ export {
17484
18957
  attachTileflowAtmosphere,
17485
18958
  attachTileflowFairUseNotice,
17486
18959
  attachTileflowMapLifecycle,
18960
+ attachTileflowNotices,
17487
18961
  createBoundaryFeatureState,
17488
18962
  createTileflowBuildings3D,
17489
18963
  createTileflowDepthMarkers,
@@ -17507,9 +18981,11 @@ export {
17507
18981
  playTileflowShot,
17508
18982
  readTileflowBuildings3D,
17509
18983
  readTileflowMapTileSource,
18984
+ readTileflowNotices,
17510
18985
  readTileflowRouteInks,
17511
18986
  readTileflowStreetLights,
17512
18987
  readTileflowSunPreset,
18988
+ readTileflowTeamModelName,
17513
18989
  readTileflowVegetation3D,
17514
18990
  readTileflowVegetationDisplay,
17515
18991
  registerTileflowContourProtocol,
@@ -17531,6 +19007,8 @@ export {
17531
19007
  tileflowLayerDomainMetadataKey,
17532
19008
  tileflowLineProgressGradient,
17533
19009
  tileflowMaplibreContourVersion,
19010
+ tileflowNoticeLimits,
19011
+ tileflowNoticesMetadataKey,
17534
19012
  tileflowPmtilesMaximumDirectoryDepth,
17535
19013
  tileflowPmtilesProtocol,
17536
19014
  tileflowRouteMetadataKey,
@@ -17544,6 +19022,7 @@ export {
17544
19022
  tileflowSunLightMatrix,
17545
19023
  tileflowSunMetadataKey,
17546
19024
  tileflowSunPosition,
19025
+ tileflowTeamModelUrl,
17547
19026
  tileflowTilePointToLngLat,
17548
19027
  tileflowTilesForBounds,
17549
19028
  tileflowTreeBytes,