leaflet-city-mapper 0.0.5 → 0.0.6

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.
@@ -8,6 +8,9 @@
8
8
 
9
9
  initialize(outerLayer, innerLayer, options) {
10
10
  L.Util.setOptions(this, options);
11
+ if (options?.enabled === undefined) {
12
+ this.options.enabled = !matchMedia('(hover: none) and (pointer: coarse)').matches;
13
+ }
11
14
  if (!outerLayer?.options || !innerLayer?.options || outerLayer === innerLayer) {
12
15
  throw new TypeError('Lens requires two distinct raster layers');
13
16
  }
@@ -40,14 +43,15 @@
40
43
  this._button = L.DomUtil.create('button', '', container);
41
44
  this._button.type = 'button';
42
45
  this._button.textContent = '🔍';
43
- this._button.title = 'Toggle lens; move the pointer over the map to reveal the layer underneath';
46
+ this._button.title = 'Toggle lens; touch or move the pointer over the map to reveal the layer underneath';
44
47
  this._button.setAttribute('aria-label', 'Toggle lens');
45
48
  this._button.style.cssText = 'display:block;border:0;border-radius:2px;padding:0 8px;min-width:30px;height:30px;background:white;color:#222;font:inherit;cursor:pointer';
46
49
  L.DomEvent.disableClickPropagation(container);
47
50
  L.DomEvent.disableScrollPropagation(container);
48
51
  L.DomEvent.on(this._button, 'click', this._toggle, this);
49
52
  this._events = {
50
- pointermove: this._onPointerMove.bind(this), pointerleave: this._hide.bind(this),
53
+ pointerdown: this._onPointerDown.bind(this), pointermove: this._onPointerMove.bind(this),
54
+ pointerleave: this._onPointerLeave.bind(this), pointercancel: this._hide.bind(this),
51
55
  focus: this._onFocus.bind(this), blur: this._hide.bind(this), keydown: this._onKeyDown.bind(this),
52
56
  };
53
57
  for (const [type, handler] of Object.entries(this._events)) {
@@ -111,8 +115,16 @@
111
115
  });
112
116
  },
113
117
 
118
+ _onPointerDown(event) {
119
+ if (event.pointerType === 'touch') this._onPointerMove(event);
120
+ },
121
+
122
+ _onPointerLeave(event) {
123
+ if (event.pointerType !== 'touch') this._hide();
124
+ },
125
+
114
126
  _onFocus() {
115
- this._show(this._map.getSize().divideBy(2));
127
+ if (this._lens.hidden) this._show(this._map.getSize().divideBy(2));
116
128
  },
117
129
 
118
130
  _onKeyDown(event) {
package/libs/leaflet.js CHANGED
@@ -11,7 +11,7 @@ const openlayers = new URLSearchParams(location.search).get('engine') === 'openl
11
11
  document.write((openlayers
12
12
  ? '<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/ol@10.6.1/ol.css">' +
13
13
  '<script src="https://cdn.jsdelivr.net/npm/ol@10.6.1/dist/ol.js"><\/script>' +
14
- '<script src="https://unpkg.com/leaflet-city-mapper@0.0.5/libs/openlayers-bindings.min.js"><\/script>'
14
+ '<script src="https://unpkg.com/leaflet-city-mapper@0.0.6/libs/openlayers-bindings.min.js"><\/script>'
15
15
  : '<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9/dist/leaflet.css"><script src="https://unpkg.com/leaflet@1.9/dist/leaflet-src.js"><\/script>'));
16
16
 
17
17
  const engineStyle = document.createElement('style');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "leaflet-city-mapper",
3
- "version": "0.0.5",
3
+ "version": "0.0.6",
4
4
  "description": "A small 3D rendering engine for leaflet",
5
5
  "type": "module",
6
6
  "main": "dist/leaflet-city-mapper.js",
@@ -0,0 +1,16 @@
1
+ import { CELL } from '../globals/CELL.js';
2
+ import { horizontalRay } from './horizontalRay.js';
3
+
4
+ export function collectIntersections(shape, x, y, z, seen, hits, end, iz) {
5
+ let ix, cell, triangle, distance;
6
+ for (ix = Math.floor(x / CELL); ix <= end; ix++) {
7
+ cell = shape.cells.get(`${ix},${iz}`);
8
+ if (!cell) continue;
9
+ for (triangle of cell) {
10
+ if (seen.has(triangle)) continue;
11
+ seen.add(triangle);
12
+ distance = horizontalRay(shape.tri, triangle, x, y, z, 1, 0);
13
+ if (distance > 1e-4) hits.push(distance);
14
+ }
15
+ }
16
+ }
@@ -1,14 +1,15 @@
1
1
  import { colliders } from '../weakmaps/colliders.js';
2
2
  import { indexTriangle } from './indexTriangle.js';
3
3
  import { readTriangle } from './readTriangle.js';
4
+ import { vertexCount } from './vertexCount.js';
4
5
 
5
6
 
6
7
  export function collider(mesh) {
7
8
  let c = colliders.get(mesh.geometry);
8
9
  if (c) return c;
9
- const m = mesh.worldMatrix, { position: { data: p }, index, normal, uv, instanceOffset } = mesh.geometry.attributes;
10
+ const m = mesh.worldMatrix, { position, index, normal, uv, instanceOffset } = mesh.geometry.attributes;
10
11
 
11
- const vertices = index ? index.count : p.length / 3;
12
+ const vertices = vertexCount(position, index);
12
13
  const count = vertices * (instanceOffset?.count ?? 1);
13
14
  const tri = /* alloc */ [];
14
15
  const nrm = /* alloc */ [];
@@ -0,0 +1,15 @@
1
+ export function countCrossings(hits) {
2
+ let ix, j, distance;
3
+ for (ix = 1; ix < hits.length; ix++) {
4
+ distance = hits[ix];
5
+ j = ix - 1;
6
+ while (j >= 0 && hits[j] > distance) hits[j + 1] = hits[j--];
7
+ hits[j + 1] = distance;
8
+ }
9
+ let crossings = 0, previous = -Infinity;
10
+ for (distance of hits) {
11
+ if (distance - previous > 1e-4) crossings++;
12
+ previous = distance;
13
+ }
14
+ return crossings;
15
+ }
@@ -1,8 +1,9 @@
1
1
  import { CELL } from '../globals/CELL.js';
2
2
 
3
3
  import { collider } from './collider.js';
4
- import { horizontalRay } from './horizontalRay.js';
5
4
  import { computeBounds } from '../geometry/computeBounds.js';
5
+ import { collectIntersections } from './collectIntersections.js';
6
+ import { countCrossings } from './countCrossings.js';
6
7
 
7
8
  export function insideMesh(mesh, x, y, z, seen, intersections) {
8
9
  const geometry = mesh.geometry;
@@ -27,27 +28,6 @@ export function insideMesh(mesh, x, y, z, seen, intersections) {
27
28
  const hits = intersections;
28
29
  hits.length = 0;
29
30
  seen.clear();
30
- let ix, cell, triangle, distance;
31
- for (ix = Math.floor(x / CELL); ix <= end; ix++) {
32
- cell = shape.cells.get(`${ix},${iz}`);
33
- if (!cell) continue;
34
- for (triangle of cell) {
35
- if (seen.has(triangle)) continue;
36
- seen.add(triangle);
37
- distance = horizontalRay(shape.tri, triangle, x, y, z, 1, 0);
38
- if (distance > 1e-4) hits.push(distance);
39
- }
40
- }
41
- for (ix = 1; ix < hits.length; ix++) {
42
- distance = hits[ix];
43
- let j = ix - 1;
44
- while (j >= 0 && hits[j] > distance) hits[j + 1] = hits[j--];
45
- hits[j + 1] = distance;
46
- }
47
- let crossings = 0, previous = -Infinity;
48
- for (distance of hits) {
49
- if (distance - previous > 1e-4) crossings++;
50
- previous = distance;
51
- }
52
- return crossings % 2 === 1;
31
+ collectIntersections(shape, x, y, z, seen, hits, end, iz);
32
+ return countCrossings(hits) % 2 === 1;
53
33
  }
@@ -20,7 +20,7 @@ export function insideWalker(context) {
20
20
  const y = root.position[1] + feet[1] * root.scale[1];
21
21
  const z = root.position[2] + feet[2] * root.scale[2];
22
22
  const layers = layersByLayer.get(shared);
23
- let other, node, child;
23
+ let other, node, child, nodes;
24
24
  for (other of layers) {
25
25
  if (other === layer || kindByLayer.get(other) === 'terrain') continue;
26
26
  if (kindByLayer.get(other) === 'traffic') {
@@ -29,7 +29,7 @@ export function insideWalker(context) {
29
29
  if (trafficCollision(other, x, y, z, 0, 0, 0, hitData)) return true;
30
30
  continue;
31
31
  }
32
- const nodes = hitData.nodes;
32
+ nodes = hitData.nodes;
33
33
  nodes.length = 0;
34
34
  if (rootByLayer.get(other)) nodes.push(rootByLayer.get(other));
35
35
  while (nodes.length) {
@@ -1,14 +1,17 @@
1
+ import { vertexCount } from './vertexCount.js';
2
+
1
3
  export function readTriangle(mesh, i, a, b, c) {
2
4
  const { position, index, instanceOffset } = mesh.geometry.attributes;
3
- const vertices = index?.count || position.count;
5
+ const size = position.size ?? 3;
6
+ const vertices = vertexCount(position, index);
4
7
  const vertex = instanceOffset ? i % vertices : i;
5
8
  const offset = instanceOffset ? Math.floor(i / vertices) * instanceOffset.size : 0;
6
9
  const x = instanceOffset?.data[offset] ?? 0;
7
10
  const y = instanceOffset?.data[offset + 1] ?? 0;
8
11
  const z = instanceOffset?.data[offset + 2] ?? 0;
9
- const ai = (index ? index.data[vertex] : vertex) * position.size;
10
- const bi = (index ? index.data[vertex + 1] : vertex + 1) * position.size;
11
- const ci = (index ? index.data[vertex + 2] : vertex + 2) * position.size;
12
+ const ai = (index ? index.data[vertex] : vertex) * size;
13
+ const bi = (index ? index.data[vertex + 1] : vertex + 1) * size;
14
+ const ci = (index ? index.data[vertex + 2] : vertex + 2) * size;
12
15
  a[0] = position.data[ai]; a[1] = position.data[ai + 1]; a[2] = position.data[ai + 2];
13
16
  b[0] = position.data[bi]; b[1] = position.data[bi + 1]; b[2] = position.data[bi + 2];
14
17
  c[0] = position.data[ci]; c[1] = position.data[ci + 1]; c[2] = position.data[ci + 2];
@@ -0,0 +1,9 @@
1
+ export function setTrafficNormal(data, x, z, distance, dx, dz) {
2
+ if (distance < 1e-6) {
3
+ data.normal[0] = -dx;
4
+ data.normal[1] = -dz;
5
+ } else {
6
+ data.normal[0] = x / distance;
7
+ data.normal[1] = z / distance;
8
+ }
9
+ }
@@ -2,13 +2,14 @@ import { rootByLayer } from '../weakmaps/rootByLayer.js';
2
2
  import { trafficByLayer } from '../weakmaps/trafficByLayer.js';
3
3
 
4
4
  import { locateTraffic } from '../traffic/locateTraffic.js';
5
+ import { setTrafficNormal } from './setTrafficNormal.js';
5
6
 
6
7
  export function trafficCollision(layer, x, y, z, dx, dz, reach, data) {
7
8
  const state = trafficByLayer.get(layer);
8
9
  if (!state?.mesh || !state.trips.length) return false;
9
10
  let bounds = state.collisionBounds;
10
11
  if (!bounds) {
11
- bounds = state.collisionBounds = { radius: 0, minY: Infinity, maxY: -Infinity };
12
+ bounds = state.collisionBounds = /* alloc */ { radius: 0, minY: Infinity, maxY: -Infinity };
12
13
  let i, px, py, pz;
13
14
  for (i = 0; i < state.template.position.length; i += 3) {
14
15
  px = state.template.position[i];
@@ -19,7 +20,7 @@ export function trafficCollision(layer, x, y, z, dx, dz, reach, data) {
19
20
  bounds.maxY = Math.max(bounds.maxY, py);
20
21
  }
21
22
  }
22
- const root = rootByLayer.get(layer), position = state.position ||= {};
23
+ const root = rootByLayer.get(layer), position = state.position ||= /* alloc */ {};
23
24
  const scale = Math.max(root.scale[0], root.scale[1], root.scale[2]);
24
25
  const radius = bounds.radius * scale + 0.35;
25
26
  let trip, projection, closest, rx, rz, perpendicular, entry, distance, bottom, top, px, pz;
@@ -48,13 +49,7 @@ export function trafficCollision(layer, x, y, z, dx, dz, reach, data) {
48
49
  data.best = entry;
49
50
  data.found = true;
50
51
  distance = Math.hypot(x + entry * dx - px, z + entry * dz - pz);
51
- if (distance < 1e-6) {
52
- data.normal[0] = -dx;
53
- data.normal[1] = -dz;
54
- } else {
55
- data.normal[0] = (x + entry * dx - px) / distance;
56
- data.normal[1] = (z + entry * dz - pz) / distance;
57
- }
52
+ setTrafficNormal(data, x + entry * dx - px, z + entry * dz - pz, distance, dx, dz);
58
53
  }
59
54
  return data.found;
60
55
  }
@@ -0,0 +1,3 @@
1
+ export function vertexCount(position, index) {
2
+ return index?.count ?? index?.data.length ?? position.count ?? position.data.length / (position.size ?? 3);
3
+ }
@@ -1,5 +1,8 @@
1
1
  import { cameraTouchStateByMap } from '../weakmaps/cameraTouchStateByMap.js';
2
+ import { cameraHandlersByMap } from '../weakmaps/cameraHandlersByMap.js';
2
3
  import { rotateControlByMap } from '../weakmaps/rotateControlByMap.js';
4
+ import { touchPitchByMap } from '../weakmaps/touchPitchByMap.js';
5
+ import { touchRotateByMap } from '../weakmaps/touchRotateByMap.js';
3
6
 
4
7
  import { L } from '../globals/L.js';
5
8
  import { Compass } from '../globals/Compass.js';
@@ -75,6 +78,8 @@ export function cameraControls(map) {
75
78
  addCameraHandler(map, 'compassBearing', Compass.value);
76
79
  addCameraHandler(map, 'touchRotate', TouchMode.value);
77
80
  addCameraHandler(map, 'touchPitch', TouchMode.value);
81
+ touchRotateByMap.set(map, cameraHandlersByMap.get(map).get('touchRotate'));
82
+ touchPitchByMap.set(map, cameraHandlersByMap.get(map).get('touchPitch'));
78
83
  const container = map.getContainer();
79
84
  const state = /* alloc */ { map, container, touch: null, nativeZoom: undefined };
80
85
  cameraTouchStateByMap.set(map, state);
@@ -86,6 +91,7 @@ export function cameraControls(map) {
86
91
  container.addEventListener('touchcancel', endCameraTouchEvent, true);
87
92
  window.addEventListener('blur', endAllCameraTouches);
88
93
  map.on('cameratouchchange', changeCameraTouch, map);
94
+ changeCameraTouch.call(map);
89
95
  if (map.options.rotateControl) {
90
96
  rotateControlByMap.set(map, /* alloc */ new L.Control.Rotate(typeof map.options.rotateControl === 'object' ? map.options.rotateControl : cameraEmptyOptions));
91
97
  rotateControlByMap.get(map).addTo(map);
@@ -9,7 +9,7 @@ export function setRasterOpacity(layer, opacity) {
9
9
  let tile, terrainMesh;
10
10
  for (tile of rasterTilesByLayer.get(layer).values()) {
11
11
  if (tile.mesh) tile.mesh.opacity = opacity;
12
- terrainMesh = rasterTilesByLayer.get(terrain3DByLayer.get(layer)).get(tile);
12
+ terrainMesh = rasterTilesByLayer.get(terrain3DByLayer.get(layer))?.get(tile);
13
13
  if (terrainMesh) terrainMesh.opacity = opacity;
14
14
  }
15
15
  update(layer);
@@ -9,7 +9,7 @@ export function setRasterZIndex(layer, zIndex) {
9
9
  let tile, terrainMesh;
10
10
  for (tile of rasterTilesByLayer.get(layer).values()) {
11
11
  if (tile.mesh) tile.mesh.renderOrder = zIndex;
12
- terrainMesh = rasterTilesByLayer.get(terrain3DByLayer.get(layer)).get(tile);
12
+ terrainMesh = rasterTilesByLayer.get(terrain3DByLayer.get(layer))?.get(tile);
13
13
  if (terrainMesh) terrainMesh.renderOrder = zIndex;
14
14
  }
15
15
  update(layer);
@@ -3,15 +3,14 @@ import { inflightByLayer } from '../weakmaps/inflightByLayer.js';
3
3
  import { cameraViewByMap } from '../weakmaps/cameraViewByMap.js';
4
4
 
5
5
 
6
- import { buildAreaFeature } from './buildAreaFeature.js';
7
6
  import { attachAreaMesh } from './attachAreaMesh.js';
8
7
  import { loadSource } from '../archives/loadSource.js';
9
8
  import { sourceAreaContext } from './sourceAreaContext.js';
10
9
  import { pump } from './pumpSourceAreas.js';
11
10
  import { buildAreaInWorker } from '../workers/buildAreaInWorker.js';
11
+ import { buildAreaFeatures } from './buildAreaFeatures.js';
12
12
 
13
13
  export async function buildArea(layer, area, map) {
14
- let feature;
15
14
  try {
16
15
  await /* alloc */ new Promise(requestAnimationFrame);
17
16
  await cameraViewByMap.get(map);
@@ -29,16 +28,8 @@ export async function buildArea(layer, area, map) {
29
28
  if (layer.options.worker !== false && typeof Worker !== 'undefined' && data.features.length >= 64) {
30
29
  context.jobs = /* alloc */ [];
31
30
  }
32
- let deadline = performance.now() + 4;
33
- for (feature of data.features) {
34
- if (area.request.signal.aborted) return;
35
- if (performance.now() >= deadline) {
36
- await /* alloc */ new Promise(requestAnimationFrame);
37
- if (area.request.signal.aborted) return;
38
- deadline = performance.now() + 4;
39
- }
40
- buildAreaFeature(context, feature);
41
- }
31
+ await buildAreaFeatures(context, area, data.features);
32
+ if (area.request.signal.aborted) return;
42
33
  if (context.jobs) await buildAreaInWorker(context, area.request.signal);
43
34
  if (area.request.signal.aborted) return;
44
35
  attachAreaMesh(context);
@@ -1,10 +1,10 @@
1
- import { L } from '../globals/L.js';
2
1
  import { buildAreaPoint } from './buildAreaPoint.js';
3
2
  import { buildAreaPolygon } from '../geometry/buildExtrudedPolygon.js';
4
3
  import { parseColor } from '../colors/parseColor.js';
5
4
  import { featureAnchor } from './featureAnchor.js';
6
5
  import { extrusionStyle } from './extrusionStyle.js';
7
6
  import { prepareAreaGeometryJob } from './prepareAreaGeometryJob.js';
7
+ import { matchesAreaTile } from './matchesAreaTile.js';
8
8
 
9
9
  export function buildAreaFeature(context, feature) {
10
10
  const { backend: layer, area, map } = context;
@@ -12,10 +12,7 @@ export function buildAreaFeature(context, feature) {
12
12
  const coordinates = featureAnchor(feature, layer.options.nightLight);
13
13
  const geometry = feature.geometry, isTree = geometry.type === 'Point';
14
14
  const lon = coordinates[0], lat = coordinates[1];
15
- if (coords) {
16
- const anchor = map.project(/* alloc */ new L.LatLng(lat, lon), coords.z);
17
- if (Math.floor(anchor.x / 256) !== coords.x || Math.floor(anchor.y / 256) !== coords.y) return;
18
- }
15
+ if (!matchesAreaTile(map, coords, lon, lat)) return;
19
16
  const style = extrusionStyle(layer, feature);
20
17
  if (style === null) {
21
18
  context.skipped++;
@@ -0,0 +1,14 @@
1
+ import { buildAreaFeature } from './buildAreaFeature.js';
2
+
3
+ export async function buildAreaFeatures(context, area, features) {
4
+ let deadline = performance.now() + 4, feature;
5
+ for (feature of features) {
6
+ if (area.request.signal.aborted) return;
7
+ if (performance.now() >= deadline) {
8
+ await /* alloc */ new Promise(requestAnimationFrame);
9
+ if (area.request.signal.aborted) return;
10
+ deadline = performance.now() + 4;
11
+ }
12
+ buildAreaFeature(context, feature);
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ import { L } from '../globals/L.js';
2
+
3
+ export function matchesAreaTile(map, coords, lon, lat) {
4
+ if (!coords) return true;
5
+ const anchor = map.project(/* alloc */ new L.LatLng(lat, lon), coords.z);
6
+ return Math.floor(anchor.x / 256) === coords.x && Math.floor(anchor.y / 256) === coords.y;
7
+ }
@@ -1,19 +1,10 @@
1
1
  import { projectedTree } from '../projections/projectTreePoint.js';
2
- import { projectRing } from '../projections/projectRing.js';
3
2
  import { parseColor } from '../colors/parseColor.js';
3
+ import { projectAreaPolygons } from './projectAreaPolygons.js';
4
4
 
5
5
  export function prepareAreaGeometryJob(context, feature, lon, lat, style, baseColor) {
6
6
  const geometry = feature.geometry;
7
- const polygons = /* alloc */ [];
8
- const rings = geometry.type === 'Polygon' ? /* alloc */ [geometry.coordinates]
9
- : geometry.type === 'MultiPolygon' ? geometry.coordinates : /* alloc */ [];
10
- let polygon, ring, paths;
11
- for (polygon of rings) {
12
- if (!polygon.length) throw /* alloc */ new Error('Empty GeoJSON polygon');
13
- paths = /* alloc */ [];
14
- for (ring of polygon) paths.push(projectRing(context, ring));
15
- polygons.push(paths);
16
- }
7
+ const polygons = projectAreaPolygons(context, geometry);
17
8
  const point = geometry.type === 'Point' ? projectedTree(context, lon, lat) : null;
18
9
  const nightLight = context.backend.options.nightLight;
19
10
  const crownColor = point ? parseColor(style.color ?? (nightLight ? undefined : 0x4f823b)) : baseColor;
@@ -0,0 +1,15 @@
1
+ import { projectRing } from '../projections/projectRing.js';
2
+
3
+ export function projectAreaPolygons(context, geometry) {
4
+ const rings = geometry.type === 'Polygon' ? /* alloc */ [geometry.coordinates]
5
+ : geometry.type === 'MultiPolygon' ? geometry.coordinates : /* alloc */ [];
6
+ const polygons = /* alloc */ [];
7
+ let polygon, ring, paths;
8
+ for (polygon of rings) {
9
+ if (!polygon.length) throw /* alloc */ new Error('Empty GeoJSON polygon');
10
+ paths = /* alloc */ [];
11
+ for (ring of polygon) paths.push(projectRing(context, ring));
12
+ polygons.push(paths);
13
+ }
14
+ return polygons;
15
+ }
@@ -9,14 +9,10 @@ import { moves } from '../globals/moves.js';
9
9
  import * as createMesh from '../rendering/createMesh.js';
10
10
  import { build } from '../geometry/buildGeometryParts.js';
11
11
 
12
- import { wrap } from '../utils/wrap.js';
13
12
  import { local } from '../projections/walkerLocalPosition.js';
14
- import { place } from './placeWalker.js';
15
- import { land } from './landWalker.js';
16
- import { moveWalker } from './moveWalker.js';
17
13
  import { stepWalkerHeight } from './stepWalkerHeight.js';
18
- import { updateWalkerView } from './updateWalkerView.js';
19
14
  import { resolveWalkerPosition } from './resolveWalkerPosition.js';
15
+ import { updateWalkerFrame } from './updateWalkerFrame.js';
20
16
 
21
17
  export function step(now) {
22
18
  const layer = this.layer;
@@ -41,12 +37,5 @@ export function step(now) {
41
37
  let forward = 0, turn = 0;
42
38
  for (key of this.keys) { forward += moves[key]?.[0] || 0; turn += moves[key]?.[1] || 0; }
43
39
  forward = Math.sign(forward); turn = Math.sign(turn);
44
- if (turn) { this.state.heading = wrap(this.state.heading + turn * 2.5 * dt); changed = true; }
45
- if (forward) {
46
- moveWalker.call(this, forward, dt);
47
- changed = true;
48
- }
49
- if (changed) land.call(this, previousY);
50
- if (updateWalkerView.call(this, forward, turn, dt)) changed = true;
51
- if (changed) place.call(this);
40
+ updateWalkerFrame(this, previousY, forward, turn, dt, changed);
52
41
  }
@@ -0,0 +1,19 @@
1
+ import { wrap } from '../utils/wrap.js';
2
+ import { place } from './placeWalker.js';
3
+ import { land } from './landWalker.js';
4
+ import { moveWalker } from './moveWalker.js';
5
+ import { updateWalkerView } from './updateWalkerView.js';
6
+
7
+ export function updateWalkerFrame(walker, previousY, forward, turn, dt, changed) {
8
+ if (turn) {
9
+ walker.state.heading = wrap(walker.state.heading + turn * 2.5 * dt);
10
+ changed = true;
11
+ }
12
+ if (forward) {
13
+ moveWalker.call(walker, forward, dt);
14
+ changed = true;
15
+ }
16
+ if (changed) land.call(walker, previousY);
17
+ if (updateWalkerView.call(walker, forward, turn, dt)) changed = true;
18
+ if (changed) place.call(walker);
19
+ }
@@ -1,5 +1,6 @@
1
1
  import { geometryWorkersByMap } from '../weakmaps/geometryWorkersByMap.js';
2
2
  import { closeGeometryWorkers } from './closeGeometryWorkers.js';
3
+ import { createGeometryWorker } from './createGeometryWorker.js';
3
4
 
4
5
  export function acquireGeometryWorker(map, url) {
5
6
  let pool = map && geometryWorkersByMap.get(map);
@@ -24,9 +25,5 @@ export function acquireGeometryWorker(map, url) {
24
25
  pool.entries.delete(entry);
25
26
  }
26
27
  }
27
- entry = /* alloc */ {
28
- url, pool, worker: /* alloc */ new Worker(url, /* alloc */ { type: 'module' }),
29
- };
30
- if (pool) pool.entries.add(entry);
31
- return entry;
28
+ return createGeometryWorker(url, pool);
32
29
  }
@@ -31,6 +31,10 @@ export async function buildAreaInWorker(context, signal) {
31
31
  if (!entry.closed) worker.terminate();
32
32
  if (pool) pool.entries.delete(entry);
33
33
  }
34
+ if (entry.blobUrl) {
35
+ URL.revokeObjectURL(entry.blobUrl);
36
+ entry.blobUrl = null;
37
+ }
34
38
  context.jobs = null;
35
39
  }
36
40
  }
@@ -0,0 +1,19 @@
1
+ export function createGeometryWorker(url, pool) {
2
+ let workerUrl = url, blobUrl, worker;
3
+ if (globalThis.location) {
4
+ const resolved = /* alloc */ new URL(url, location.href);
5
+ if ((resolved.protocol === 'https:' || resolved.protocol === 'http:') && resolved.origin !== location.origin) {
6
+ blobUrl = URL.createObjectURL(/* alloc */ new Blob(/* alloc */ [`import ${JSON.stringify(resolved.href)};`],
7
+ /* alloc */ { type: 'text/javascript' }));
8
+ workerUrl = blobUrl;
9
+ }
10
+ }
11
+ try {
12
+ worker = /* alloc */ new Worker(workerUrl, /* alloc */ { type: 'module' });
13
+ } finally {
14
+ if (!worker && blobUrl) URL.revokeObjectURL(blobUrl);
15
+ }
16
+ const entry = /* alloc */ { url, pool, worker, blobUrl };
17
+ if (pool) pool.entries.add(entry);
18
+ return entry;
19
+ }