Skip to content

Commit fde7fee

Browse files
nicholascowanNicholas Cowan
and
Nicholas Cowan
authored
feat(viewer): honor pitch and bearing in addition to zoom (#188)
Co-authored-by: Nicholas Cowan <[email protected]>
1 parent a2202ff commit fde7fee

File tree

6 files changed

+52
-15
lines changed

6 files changed

+52
-15
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ dist
66
cdk.context.json
77
test-data
88
*.ndjson
9+
package-lock.json
10+
yarn-error.log

packages/viewer/src/bounds.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,6 @@ export interface LonLat {
5252

5353
export interface MapLocation extends LonLat {
5454
zoom: number;
55+
pitch: number;
56+
bearing: number;
5557
}

packages/viewer/src/map.protocol.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ function urlToXyzParams(url: string): null | MapParams {
3232
const x = Number(chunks[6]);
3333
const y = Number(chunks[7]);
3434

35-
if (isNaN(x) || isNaN(x) || isNaN(z)) return null;
35+
const rasterFillColor = chunks[8];
3636

37-
return { seed, difficulty, act, x, y, z };
37+
if (isNaN(x) || isNaN(y) || isNaN(z)) return null;
38+
39+
return { seed, difficulty, act, x, y, z, rasterFillColor };
3840
}
3941

4042
export type Cancel = { cancel: () => void };

packages/viewer/src/map.ts

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export class Diablo2MapViewer {
1212
difficulty = Difficulty.Nightmare;
1313
act = Act.ActV;
1414
seed = 0x00ff00ff;
15+
color = 'white';
1516
updateUrlTimer: unknown;
1617

1718
constructor(el: string) {
@@ -44,33 +45,44 @@ export class Diablo2MapViewer {
4445
}
4546

4647
/**
47-
* Support parsing of zooms with `z14` or `14z`
48-
* @param zoom string to parse zoom from
48+
* Support parsing of zooms, pitches, bearings with `z14` or `14z`, etc
49+
* @param value string to parse value from
50+
* @param prefixSuffix prefix or suffix for the map property
4951
*/
50-
parseZoom(zoom: string | null): number {
51-
if (zoom == null || zoom === '') return NaN;
52-
if (zoom.startsWith('z')) return parseFloat(zoom.slice(1));
53-
if (zoom.endsWith('z')) return parseFloat(zoom);
52+
parseMapControlValue(value: string | null, prefixSuffix: string): number {
53+
if (value == null || value === '') return NaN;
54+
if (value.startsWith(prefixSuffix)) return parseFloat(value.slice(1));
55+
if (value.endsWith(prefixSuffix)) return parseFloat(value);
5456
return NaN;
5557
}
5658

5759
/** Parse a location from window.hash if it exists */
5860
fromHash(str: string): Partial<MapLocation> {
5961
const output: Partial<MapLocation> = {};
6062
const hash = str.replace('#@', '');
61-
const [latS, lonS, zoomS] = hash.split(',');
63+
const [latS, lonS, zoomS, pitchS, bearingS] = hash.split(',');
6264
const lat = parseFloat(latS);
6365
const lon = parseFloat(lonS);
6466
if (!isNaN(lat) && !isNaN(lon)) {
6567
output.lat = lat;
6668
output.lon = lon;
6769
}
6870

69-
const newZoom = this.parseZoom(zoomS);
71+
const newZoom = this.parseMapControlValue(zoomS, 'z');
7072
if (!isNaN(newZoom)) {
7173
output.zoom = newZoom;
7274
}
7375

76+
const newPitch = this.parseMapControlValue(pitchS, 'p');
77+
if (!isNaN(newPitch)) {
78+
output.pitch = newPitch;
79+
}
80+
81+
const newBearing = this.parseMapControlValue(bearingS, 'b');
82+
if (!isNaN(newBearing)) {
83+
output.bearing = newBearing;
84+
}
85+
7486
return output;
7587
}
7688

@@ -81,11 +93,14 @@ export class Diablo2MapViewer {
8193
if (isNaN(this.seed) || this.seed <= 0) this.seed = 0x00ff00ff;
8294
this.act = ActUtil.fromString(urlParams.get('act')) ?? Act.ActI;
8395
this.difficulty = DifficultyUtil.fromString(urlParams.get('difficulty')) ?? Difficulty.Normal;
96+
this.color = urlParams.get('color') || 'white';
8497

8598
if (window.location.hash == null) return;
8699

87100
const location = this.fromHash(window.location.hash);
88101
if (location.zoom) this.map.setZoom(location.zoom);
102+
if (location.pitch) this.map.setPitch(location.pitch);
103+
if (location.bearing) this.map.setBearing(location.bearing);
89104
if (location.lat) this.map.setCenter(location);
90105
}
91106

@@ -94,13 +109,17 @@ export class Diablo2MapViewer {
94109
urlParams.set('seed', toHex(this.seed, 8));
95110
urlParams.set('act', Act[this.act]);
96111
urlParams.set('difficulty', Difficulty[this.difficulty]);
112+
urlParams.set('color', this.color);
97113
const center = this.map.getCenter();
98114
if (center == null) throw new Error('Invalid Map location');
99115
const zoom = Math.floor((this.map.getZoom() ?? 0) * 10e3) / 10e3;
116+
const pitch = Math.floor((this.map.getPitch() ?? 0) * 10e3) / 10e3;
117+
const bearing = Math.floor((this.map.getBearing() ?? 0) * 10e3) / 10e3;
118+
100119
window.history.replaceState(
101120
null,
102121
'',
103-
'?' + urlParams.toString() + `#@${center.lat.toFixed(7)},${center.lng.toFixed(7)},z${zoom}`,
122+
'?' + urlParams.toString() + `#@${center.lat.toFixed(7)},${center.lng.toFixed(7)},z${zoom},p${pitch},b${bearing}`,
104123
);
105124
this.updateUrlTimer = null;
106125
}
@@ -114,7 +133,7 @@ export class Diablo2MapViewer {
114133
update(): void {
115134
this.updateUrl();
116135
this.updateDom();
117-
const d2Url = `${toHex(this.seed, 8)}/${Difficulty[this.difficulty]}/${Act[this.act]}/{z}/{x}/{y}`;
136+
const d2Url = `${toHex(this.seed, 8)}/${Difficulty[this.difficulty]}/${Act[this.act]}/{z}/{x}/{y}/${this.color}`;
118137
if (this.lastUrl === d2Url) return;
119138
this.lastUrl = d2Url;
120139

@@ -187,4 +206,5 @@ export class Diablo2MapViewer {
187206
}
188207
});
189208
}
209+
190210
}

packages/viewer/src/render.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Bounds } from './bounds';
22
import { Diablo2Level } from '@diablo2/data';
3+
import { MapParams } from './tile';
34

45
function isInBounds(pt: { x: number; y: number }, bounds: Bounds): boolean {
56
if (pt.x < bounds.x) return false;
@@ -12,8 +13,17 @@ function isInBounds(pt: { x: number; y: number }, bounds: Bounds): boolean {
1213
export class LevelRender {
1314
static ExitSize = 12;
1415

15-
static render(level: Diablo2Level, ctx: CanvasRenderingContext2D, bounds: Bounds, scale = 0.5): void {
16+
static render(
17+
level: Diablo2Level,
18+
ctx: CanvasRenderingContext2D,
19+
bounds: Bounds,
20+
scale = 0.5,
21+
mapParams?: MapParams,
22+
): void {
1623
ctx.fillStyle = 'white';
24+
25+
if (mapParams?.rasterFillColor) ctx.fillStyle = String(mapParams?.rasterFillColor).replace('0x', '#');
26+
1727
const map = level.map;
1828

1929
for (let yOffset = 0; yOffset < map.length; yOffset++) {

packages/viewer/src/tile.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export interface MapParams {
1111
x: number;
1212
y: number;
1313
z: number;
14+
rasterFillColor: string;
1415
}
1516

1617
process = typeof process === 'undefined' ? ({ env: {} } as any) : process;
@@ -48,7 +49,7 @@ export class Diablo2MapTiles {
4849
}
4950

5051
static getRaster(d: MapParams): Promise<unknown> {
51-
const tileId = ['raster', toHex(d.difficulty, 8), Act[d.act], d.seed, d.z, d.x, d.y].join('__');
52+
const tileId = ['raster', toHex(d.difficulty, 8), Act[d.act], d.seed, d.z, d.x, d.y, d.rasterFillColor].join('__');
5253
let existing = this.tiles.get(tileId);
5354
if (existing == null) {
5455
existing = this.tileRaster(d);
@@ -78,7 +79,7 @@ export class Diablo2MapTiles {
7879
if (ctx == null) return;
7980

8081
// console.time('RenderLevel:' + tileId);
81-
for (const zone of zones) LevelRender.render(zone, ctx, bounds, (1 / scale) * 2);
82+
for (const zone of zones) LevelRender.render(zone, ctx, bounds, (1 / scale) * 2, d);
8283
// console.timeEnd('RenderLevel:' + tileId);
8384

8485
// TODO 99% of the time for rendering a map is this conversion into a PNG

0 commit comments

Comments
 (0)