world.ts 0.1.0
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/.eslintrc +48 -0
- package/.prettierrc +3 -0
- package/dist/assets/image-load-worker-nFfjtK8d.js +2 -0
- package/dist/assets/image-load-worker-nFfjtK8d.js.map +1 -0
- package/dist/buffer.d.ts +14 -0
- package/dist/common.d.ts +2 -0
- package/dist/constants.d.ts +1 -0
- package/dist/control.d.ts +5 -0
- package/dist/depth-buffer.d.ts +13 -0
- package/dist/elevation.d.ts +13 -0
- package/dist/image-load-worker.d.ts +1 -0
- package/dist/image-load.d.ts +11 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1535 -0
- package/dist/index.js.js +1535 -0
- package/dist/index.js.js.map +1 -0
- package/dist/index.js.map +1 -0
- package/dist/layers/index.d.ts +36 -0
- package/dist/layers/line/index.d.ts +18 -0
- package/dist/layers/mesh/index.d.ts +4 -0
- package/dist/layers/terrain/image-texture.d.ts +11 -0
- package/dist/layers/terrain/index.d.ts +13 -0
- package/dist/layers/terrain/texture.d.ts +10 -0
- package/dist/layers/terrain/tile-cache.d.ts +14 -0
- package/dist/layers/terrain/tile-downsampler.d.ts +16 -0
- package/dist/layers/terrain/tile-index-cache.d.ts +21 -0
- package/dist/layers/terrain/tile-shapes.d.ts +8 -0
- package/dist/layers/utils.d.ts +3 -0
- package/dist/math.d.ts +7 -0
- package/dist/program.d.ts +76 -0
- package/dist/subscriber.d.ts +1 -0
- package/dist/viewport.d.ts +24 -0
- package/dist/world.d.ts +59 -0
- package/package.json +29 -0
- package/src/buffer.ts +36 -0
- package/src/common.ts +13 -0
- package/src/constants.ts +1 -0
- package/src/control.ts +83 -0
- package/src/depth-buffer.ts +92 -0
- package/src/elevation.ts +75 -0
- package/src/glsl.d.ts +4 -0
- package/src/image-load-worker.ts +32 -0
- package/src/image-load.ts +41 -0
- package/src/index.ts +1 -0
- package/src/layers/depth.glsl +22 -0
- package/src/layers/index.ts +38 -0
- package/src/layers/line/fragment.glsl +10 -0
- package/src/layers/line/index.ts +261 -0
- package/src/layers/line/vertex.glsl +63 -0
- package/src/layers/mesh/fragment.glsl +10 -0
- package/src/layers/mesh/index.ts +258 -0
- package/src/layers/mesh/vertex.glsl +35 -0
- package/src/layers/terrain/fragment.glsl +11 -0
- package/src/layers/terrain/image-texture.ts +53 -0
- package/src/layers/terrain/index.ts +307 -0
- package/src/layers/terrain/texture.ts +27 -0
- package/src/layers/terrain/tile-cache.ts +73 -0
- package/src/layers/terrain/tile-downsampler.ts +36 -0
- package/src/layers/terrain/tile-index-cache.ts +44 -0
- package/src/layers/terrain/tile-shapes.ts +45 -0
- package/src/layers/terrain/vertex.glsl +34 -0
- package/src/layers/utils.ts +5 -0
- package/src/math.ts +39 -0
- package/src/program.ts +200 -0
- package/src/subscriber.ts +17 -0
- package/src/viewport.ts +132 -0
- package/src/world.ts +192 -0
- package/tsconfig.json +13 -0
- package/vite.config.ts +18 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { quat, vec3, vec4 } from "gl-matrix";
|
|
2
|
+
import type { Viewport } from "../viewport";
|
|
3
|
+
import type { LineLayer } from "./line";
|
|
4
|
+
import type { MeshLayer } from "./mesh";
|
|
5
|
+
import type { TerrainLayer } from "./terrain";
|
|
6
|
+
export type Terrain = {
|
|
7
|
+
readonly terrainUrl: string;
|
|
8
|
+
readonly imageryUrl: string;
|
|
9
|
+
};
|
|
10
|
+
export type Mesh = {
|
|
11
|
+
vertices: vec3[];
|
|
12
|
+
indices: vec3[];
|
|
13
|
+
position: vec3;
|
|
14
|
+
orientation: quat;
|
|
15
|
+
color: vec4;
|
|
16
|
+
size: number;
|
|
17
|
+
minSizePixels?: number;
|
|
18
|
+
maxSizePixels?: number;
|
|
19
|
+
pickable: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type Line = {
|
|
22
|
+
points: vec3[];
|
|
23
|
+
color: vec4;
|
|
24
|
+
width: number;
|
|
25
|
+
minWidthPixels?: number | undefined;
|
|
26
|
+
maxWidthPixels?: number | undefined;
|
|
27
|
+
};
|
|
28
|
+
export type BaseLayer = {
|
|
29
|
+
render: (_: {
|
|
30
|
+
viewport: Viewport;
|
|
31
|
+
depth?: boolean;
|
|
32
|
+
index?: number;
|
|
33
|
+
}) => void;
|
|
34
|
+
destroy: () => void;
|
|
35
|
+
};
|
|
36
|
+
export type Layer = TerrainLayer | MeshLayer | LineLayer;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { vec4 } from "gl-matrix";
|
|
2
|
+
import { vec3 } from "gl-matrix";
|
|
3
|
+
import type { Viewport } from "../../viewport";
|
|
4
|
+
import type { BaseLayer, Line } from "../";
|
|
5
|
+
export type LineLayer = BaseLayer & Line;
|
|
6
|
+
export declare const createLineLayer: (gl: WebGL2RenderingContext, line: Partial<Line>) => {
|
|
7
|
+
render: ({ viewport: { projection, modelView, camera, screen }, depth, index, }: {
|
|
8
|
+
viewport: Viewport;
|
|
9
|
+
depth?: boolean | undefined;
|
|
10
|
+
index?: number | undefined;
|
|
11
|
+
}) => void;
|
|
12
|
+
destroy: () => void;
|
|
13
|
+
points: vec3[];
|
|
14
|
+
color: vec4;
|
|
15
|
+
width: number;
|
|
16
|
+
minWidthPixels: number | undefined;
|
|
17
|
+
maxWidthPixels: number | undefined;
|
|
18
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Viewport } from "../../viewport";
|
|
2
|
+
import type { BaseLayer, Terrain } from "..";
|
|
3
|
+
export type TerrainLayer = BaseLayer & Terrain;
|
|
4
|
+
export declare const createTerrainLayer: (gl: WebGL2RenderingContext, terrain: Partial<Terrain>) => {
|
|
5
|
+
terrainUrl: string;
|
|
6
|
+
imageryUrl: string;
|
|
7
|
+
render: ({ viewport, depth, index, }: {
|
|
8
|
+
viewport: Viewport;
|
|
9
|
+
depth?: boolean | undefined;
|
|
10
|
+
index?: number | undefined;
|
|
11
|
+
}) => void;
|
|
12
|
+
destroy: () => void;
|
|
13
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { vec3 } from "gl-matrix";
|
|
2
|
+
import type { Texture } from "./texture";
|
|
3
|
+
export type TileCache = {
|
|
4
|
+
get: (xyz: vec3) => Texture | undefined;
|
|
5
|
+
destroy: () => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const createTileCache: ({ gl, urlPattern, onLoad, }: {
|
|
8
|
+
gl: WebGL2RenderingContext;
|
|
9
|
+
urlPattern: string;
|
|
10
|
+
onLoad?: (() => void) | undefined;
|
|
11
|
+
}) => {
|
|
12
|
+
get: (xyz: vec3) => Texture | undefined;
|
|
13
|
+
destroy: () => void;
|
|
14
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { vec3 } from "gl-matrix";
|
|
2
|
+
import type { Texture } from "./texture";
|
|
3
|
+
import type { TileCache } from "./tile-cache";
|
|
4
|
+
export type DownsampledTile = {
|
|
5
|
+
texture: Texture;
|
|
6
|
+
downsample: number;
|
|
7
|
+
};
|
|
8
|
+
export type TileDownsampler = {
|
|
9
|
+
get: (xyz: vec3) => DownsampledTile | undefined;
|
|
10
|
+
};
|
|
11
|
+
export declare const createTileDownsampler: (cache: TileCache, initialDownsample?: number) => {
|
|
12
|
+
get: ([x, y, z]: vec3) => {
|
|
13
|
+
texture: Texture;
|
|
14
|
+
downsample: number;
|
|
15
|
+
} | undefined;
|
|
16
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { vec3 } from "gl-matrix";
|
|
2
|
+
import LRUCache from "lru-cache";
|
|
3
|
+
export type TileIndexCache<T> = {
|
|
4
|
+
get: (xyz: vec3) => T | undefined;
|
|
5
|
+
set: (xyz: vec3, value: T) => void;
|
|
6
|
+
delete: (xyz: vec3) => void;
|
|
7
|
+
clear: () => void;
|
|
8
|
+
purgeStale: () => void;
|
|
9
|
+
};
|
|
10
|
+
export type CreateTileIndexCacheOptions<T> = {
|
|
11
|
+
max: number;
|
|
12
|
+
ttl?: number;
|
|
13
|
+
dispose?: (value: T, key: vec3) => void;
|
|
14
|
+
};
|
|
15
|
+
export declare const createTileIndexCache: <T>(options: CreateTileIndexCacheOptions<T>) => {
|
|
16
|
+
get: (xyz: vec3) => T | undefined;
|
|
17
|
+
set: (xyz: vec3, value: T) => LRUCache<number, T>;
|
|
18
|
+
delete: (xyz: vec3) => boolean;
|
|
19
|
+
clear: () => void;
|
|
20
|
+
purgeStale: () => boolean;
|
|
21
|
+
};
|
package/dist/math.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { vec3 } from "gl-matrix";
|
|
2
|
+
export declare const radians: (_: number) => number;
|
|
3
|
+
export declare const degrees: (_: number) => number;
|
|
4
|
+
export declare const quadratic: (a: number, b: number, c: number) => number[];
|
|
5
|
+
export declare const mercator: ([lng, lat, alt]: vec3, out?: vec3) => vec3;
|
|
6
|
+
export declare const geodetic: ([x, y, z]: vec3, out?: vec3) => vec3;
|
|
7
|
+
export declare const tileToMercator: ([x, y, z]: vec3, out?: vec3) => vec3;
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { mat4, vec2, vec3, vec4 } from "gl-matrix";
|
|
2
|
+
import type { Buffer } from "./buffer";
|
|
3
|
+
export type Program = {
|
|
4
|
+
use: () => void;
|
|
5
|
+
uniform1f: (name: string) => Uniform<number>;
|
|
6
|
+
uniform1i: (name: string) => Uniform<number>;
|
|
7
|
+
uniform2f: (name: string) => Uniform<vec2>;
|
|
8
|
+
uniform2i: (name: string) => Uniform<vec2>;
|
|
9
|
+
uniform3f: (name: string) => Uniform<vec3>;
|
|
10
|
+
uniform3i: (name: string) => Uniform<vec3>;
|
|
11
|
+
uniform4f: (name: string) => Uniform<vec4>;
|
|
12
|
+
uniform4i: (name: string) => Uniform<vec4>;
|
|
13
|
+
uniformMatrix4f: (name: string) => Uniform<mat4>;
|
|
14
|
+
attribute3f: (name: string, buffer: Buffer, _?: {
|
|
15
|
+
stride?: number;
|
|
16
|
+
offset?: number;
|
|
17
|
+
}) => Attribute;
|
|
18
|
+
attribute2f: (name: string, buffer: Buffer, _?: {
|
|
19
|
+
stride?: number;
|
|
20
|
+
offset?: number;
|
|
21
|
+
}) => Attribute;
|
|
22
|
+
destroy: () => void;
|
|
23
|
+
};
|
|
24
|
+
export type Uniform<T> = {
|
|
25
|
+
set: (value: T) => void;
|
|
26
|
+
};
|
|
27
|
+
export type Attribute = {
|
|
28
|
+
use: () => void;
|
|
29
|
+
};
|
|
30
|
+
export declare const createProgram: ({ gl, vertexSource, fragmentSource, }: {
|
|
31
|
+
gl: WebGL2RenderingContext;
|
|
32
|
+
vertexSource: string;
|
|
33
|
+
fragmentSource: string;
|
|
34
|
+
}) => {
|
|
35
|
+
use: () => void;
|
|
36
|
+
uniform1f: (name: string) => {
|
|
37
|
+
set: (value: number) => void;
|
|
38
|
+
};
|
|
39
|
+
uniform1i: (name: string) => {
|
|
40
|
+
set: (value: number) => void;
|
|
41
|
+
};
|
|
42
|
+
uniform2f: (name: string) => {
|
|
43
|
+
set: (value: vec2) => void;
|
|
44
|
+
};
|
|
45
|
+
uniform2i: (name: string) => {
|
|
46
|
+
set: (value: vec2) => void;
|
|
47
|
+
};
|
|
48
|
+
uniform3f: (name: string) => {
|
|
49
|
+
set: (value: vec3) => void;
|
|
50
|
+
};
|
|
51
|
+
uniform3i: (name: string) => {
|
|
52
|
+
set: (value: vec3) => void;
|
|
53
|
+
};
|
|
54
|
+
uniform4f: (name: string) => {
|
|
55
|
+
set: (value: vec4) => void;
|
|
56
|
+
};
|
|
57
|
+
uniform4i: (name: string) => {
|
|
58
|
+
set: (value: vec4) => void;
|
|
59
|
+
};
|
|
60
|
+
uniformMatrix4f: (name: string) => {
|
|
61
|
+
set: (value: mat4) => void;
|
|
62
|
+
};
|
|
63
|
+
attribute2f: (name: string, buffer: Buffer, options?: {
|
|
64
|
+
stride?: number;
|
|
65
|
+
offset?: number;
|
|
66
|
+
}) => {
|
|
67
|
+
use: () => void;
|
|
68
|
+
};
|
|
69
|
+
attribute3f: (name: string, buffer: Buffer, options?: {
|
|
70
|
+
stride?: number;
|
|
71
|
+
offset?: number;
|
|
72
|
+
}) => {
|
|
73
|
+
use: () => void;
|
|
74
|
+
};
|
|
75
|
+
destroy: () => void;
|
|
76
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createSubscriber: <T>() => readonly [(handler: (msg: T) => void) => () => void, (msg: T) => void];
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { mat4, vec2, vec3, vec4 } from "gl-matrix";
|
|
2
|
+
export type Orientation = [pitch: number, roll: number, yaw: number];
|
|
3
|
+
export type View = {
|
|
4
|
+
target: vec3;
|
|
5
|
+
center?: vec2;
|
|
6
|
+
screen: vec2;
|
|
7
|
+
distance: number;
|
|
8
|
+
orientation: Orientation;
|
|
9
|
+
fieldOfView?: number;
|
|
10
|
+
};
|
|
11
|
+
export type Viewport = {
|
|
12
|
+
camera: vec3;
|
|
13
|
+
screen: vec2;
|
|
14
|
+
projection: mat4;
|
|
15
|
+
modelView: mat4;
|
|
16
|
+
scale: (_: number) => Viewport;
|
|
17
|
+
screenToClip: (_: vec2, out?: vec4) => vec4;
|
|
18
|
+
clipToScreen: (_: vec4, out?: vec2) => vec2;
|
|
19
|
+
clipToLocal: (_: vec4, out?: vec3) => vec3;
|
|
20
|
+
localToClip: (_: vec3, out?: vec4) => vec4;
|
|
21
|
+
localToWorld: (_: vec3, out?: vec3) => vec3;
|
|
22
|
+
worldToLocal: (_: vec3, out?: vec3) => vec3;
|
|
23
|
+
};
|
|
24
|
+
export declare const createViewport: (view: View) => Viewport;
|
package/dist/world.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { vec2 } from "gl-matrix";
|
|
2
|
+
import { vec3 } from "gl-matrix";
|
|
3
|
+
import type { Layer, Line, Mesh, Terrain } from "./layers";
|
|
4
|
+
import type { View } from "./viewport";
|
|
5
|
+
type Pick = {
|
|
6
|
+
position: vec3;
|
|
7
|
+
layer: Layer | undefined;
|
|
8
|
+
};
|
|
9
|
+
type PickHandler = (_: Pick) => void;
|
|
10
|
+
export type World = {
|
|
11
|
+
set view(_: View);
|
|
12
|
+
get view(): View;
|
|
13
|
+
addTerrain: (_: Partial<Terrain>) => Terrain;
|
|
14
|
+
addMesh: (_: Partial<Mesh>) => Mesh;
|
|
15
|
+
addLine: (_: Partial<Line>) => Line;
|
|
16
|
+
recenter: ([x, y]: [number, number]) => void;
|
|
17
|
+
pick: ([x, y]: [number, number]) => Pick;
|
|
18
|
+
onMouseDown: (_: PickHandler) => void;
|
|
19
|
+
onMouseUp: (_: PickHandler) => void;
|
|
20
|
+
onMouseMove: (_: PickHandler) => void;
|
|
21
|
+
destroy: () => void;
|
|
22
|
+
};
|
|
23
|
+
export declare const createWorld: (canvas: HTMLCanvasElement) => {
|
|
24
|
+
view: View;
|
|
25
|
+
addTerrain: (terrain: Partial<Terrain>) => {
|
|
26
|
+
terrainUrl: string;
|
|
27
|
+
imageryUrl: string;
|
|
28
|
+
render: ({ viewport, depth, index, }: {
|
|
29
|
+
viewport: import("./viewport").Viewport;
|
|
30
|
+
depth?: boolean | undefined;
|
|
31
|
+
index?: number | undefined;
|
|
32
|
+
}) => void;
|
|
33
|
+
destroy: () => void;
|
|
34
|
+
};
|
|
35
|
+
addMesh: (mesh: Partial<Mesh>) => import("./layers/mesh").MeshLayer;
|
|
36
|
+
addLine: (line: Partial<Line>) => {
|
|
37
|
+
render: ({ viewport: { projection, modelView, camera, screen }, depth, index, }: {
|
|
38
|
+
viewport: import("./viewport").Viewport;
|
|
39
|
+
depth?: boolean | undefined;
|
|
40
|
+
index?: number | undefined;
|
|
41
|
+
}) => void;
|
|
42
|
+
destroy: () => void;
|
|
43
|
+
points: vec3[];
|
|
44
|
+
color: import("gl-matrix").vec4;
|
|
45
|
+
width: number;
|
|
46
|
+
minWidthPixels: number | undefined;
|
|
47
|
+
maxWidthPixels: number | undefined;
|
|
48
|
+
};
|
|
49
|
+
recenter: (center: vec2) => void;
|
|
50
|
+
pick: ([screenX, screenY]: vec2) => {
|
|
51
|
+
position: vec3;
|
|
52
|
+
layer: Layer | undefined;
|
|
53
|
+
};
|
|
54
|
+
onMouseDown: (handler: (msg: Pick) => void) => () => void;
|
|
55
|
+
onMouseUp: (handler: (msg: Pick) => void) => () => void;
|
|
56
|
+
onMouseMove: (handler: (msg: Pick) => void) => () => void;
|
|
57
|
+
destroy: () => void;
|
|
58
|
+
};
|
|
59
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "world.ts",
|
|
3
|
+
"type": "module",
|
|
4
|
+
"main": "dist/world.js",
|
|
5
|
+
"types": "dist/index.d.ts",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"lint": "eslint --max-warnings 0 --cache --cache-strategy content src"
|
|
9
|
+
},
|
|
10
|
+
"devDependencies": {
|
|
11
|
+
"@types/lru-cache": "^7.5.0",
|
|
12
|
+
"@typescript-eslint/eslint-plugin": "^6.15.0",
|
|
13
|
+
"eslint": "^8.56.0",
|
|
14
|
+
"eslint-config-prettier": "^9.1.0",
|
|
15
|
+
"eslint-plugin-no-async-without-await": "^1.2.0",
|
|
16
|
+
"eslint-plugin-prettier": "^5.1.1",
|
|
17
|
+
"eslint-plugin-simple-import-sort": "^10.0.0",
|
|
18
|
+
"eslint-plugin-unused-imports": "^3.0.0",
|
|
19
|
+
"typescript": "^5.3.3",
|
|
20
|
+
"vite": "^5.1.0",
|
|
21
|
+
"vite-plugin-dts": "^3.7.2",
|
|
22
|
+
"vite-plugin-glsl": "^1.2.1"
|
|
23
|
+
},
|
|
24
|
+
"dependencies": {
|
|
25
|
+
"gl-matrix": "^3.4.3",
|
|
26
|
+
"lru-cache": "^7.6.0"
|
|
27
|
+
},
|
|
28
|
+
"version": "0.1.0"
|
|
29
|
+
}
|
package/src/buffer.ts
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type Buffer = {
|
|
2
|
+
set: (value: number[]) => void;
|
|
3
|
+
use: () => void;
|
|
4
|
+
destroy: () => void;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
export const createBuffer = ({
|
|
8
|
+
gl,
|
|
9
|
+
type,
|
|
10
|
+
target,
|
|
11
|
+
}: {
|
|
12
|
+
gl: WebGL2RenderingContext;
|
|
13
|
+
type: "f32" | "u16";
|
|
14
|
+
target: "array" | "element";
|
|
15
|
+
}) => {
|
|
16
|
+
const buffer = gl.createBuffer();
|
|
17
|
+
if (!buffer) throw new Error("Buffer creation failed");
|
|
18
|
+
|
|
19
|
+
const glTarget =
|
|
20
|
+
target === "array" ? gl.ARRAY_BUFFER : gl.ELEMENT_ARRAY_BUFFER;
|
|
21
|
+
|
|
22
|
+
const use = () => gl.bindBuffer(glTarget, buffer);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
set: value => {
|
|
26
|
+
use();
|
|
27
|
+
gl.bufferData(
|
|
28
|
+
glTarget,
|
|
29
|
+
type === "u16" ? new Uint16Array(value) : new Float32Array(value),
|
|
30
|
+
gl.DYNAMIC_DRAW,
|
|
31
|
+
);
|
|
32
|
+
},
|
|
33
|
+
use,
|
|
34
|
+
destroy: () => gl.deleteBuffer(buffer),
|
|
35
|
+
} satisfies Buffer;
|
|
36
|
+
};
|
package/src/common.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const debounce = <F extends (...args: unknown[]) => void>(
|
|
2
|
+
f: F,
|
|
3
|
+
delay: number,
|
|
4
|
+
) => {
|
|
5
|
+
let timeout: number;
|
|
6
|
+
return (...args: Parameters<F>) => {
|
|
7
|
+
clearTimeout(timeout);
|
|
8
|
+
timeout = setTimeout(() => f(args), delay);
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const range = (start: number, end: number) =>
|
|
13
|
+
Array.from({ length: end - start }, (_, k) => k + start);
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const circumference = 40075017;
|
package/src/control.ts
ADDED
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { debounce } from "./common";
|
|
2
|
+
import { circumference } from "./constants";
|
|
3
|
+
import type { Orientation } from "./viewport";
|
|
4
|
+
import type { World } from "./world";
|
|
5
|
+
|
|
6
|
+
const minimumDistance = 2;
|
|
7
|
+
|
|
8
|
+
export const createMouseControl = (canvas: HTMLCanvasElement, world: World) => {
|
|
9
|
+
let enabled = true;
|
|
10
|
+
let zooming = false;
|
|
11
|
+
|
|
12
|
+
const onMouseDown = ({ x, y }: MouseEvent) => {
|
|
13
|
+
if (!enabled) return;
|
|
14
|
+
world.recenter([x, y]);
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const onMouseMove = ({ buttons, movementX, movementY, x, y }: MouseEvent) => {
|
|
18
|
+
if (!enabled) return;
|
|
19
|
+
if (buttons === 1)
|
|
20
|
+
world.view = {
|
|
21
|
+
...world.view,
|
|
22
|
+
center: [x, y],
|
|
23
|
+
};
|
|
24
|
+
else if (buttons === 2) {
|
|
25
|
+
const {
|
|
26
|
+
screen: [width = 0, height = 0],
|
|
27
|
+
orientation: [pitch, roll, yaw],
|
|
28
|
+
} = world.view;
|
|
29
|
+
const orientation = [
|
|
30
|
+
pitch - (movementY / height) * Math.PI,
|
|
31
|
+
roll,
|
|
32
|
+
yaw - (movementX / width) * Math.PI,
|
|
33
|
+
] satisfies Orientation;
|
|
34
|
+
world.view = {
|
|
35
|
+
...world.view,
|
|
36
|
+
orientation,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const clearZooming = debounce(() => (zooming = false), 100);
|
|
42
|
+
|
|
43
|
+
const onWheel = ({ x, y, deltaY }: WheelEvent) => {
|
|
44
|
+
if (!enabled) return;
|
|
45
|
+
if (!zooming) {
|
|
46
|
+
world.recenter([x, y]);
|
|
47
|
+
zooming = true;
|
|
48
|
+
}
|
|
49
|
+
const distance = Math.min(
|
|
50
|
+
Math.max(world.view.distance * Math.exp(deltaY * 0.001), minimumDistance),
|
|
51
|
+
circumference,
|
|
52
|
+
);
|
|
53
|
+
world.view = {
|
|
54
|
+
...world.view,
|
|
55
|
+
distance,
|
|
56
|
+
};
|
|
57
|
+
clearZooming();
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const onContextMenu = (event: MouseEvent) => event.preventDefault();
|
|
61
|
+
|
|
62
|
+
canvas.addEventListener("mousedown", onMouseDown);
|
|
63
|
+
canvas.addEventListener("mousemove", onMouseMove);
|
|
64
|
+
canvas.addEventListener("wheel", onWheel, { passive: true });
|
|
65
|
+
canvas.addEventListener("contextmenu", onContextMenu);
|
|
66
|
+
|
|
67
|
+
const destroy = () => {
|
|
68
|
+
canvas.removeEventListener("mousedown", onMouseDown);
|
|
69
|
+
canvas.removeEventListener("mousemove", onMouseMove);
|
|
70
|
+
canvas.removeEventListener("wheel", onWheel);
|
|
71
|
+
canvas.removeEventListener("contextmenu", onContextMenu);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
get enabled() {
|
|
76
|
+
return enabled;
|
|
77
|
+
},
|
|
78
|
+
set enabled(_: boolean) {
|
|
79
|
+
enabled = _;
|
|
80
|
+
},
|
|
81
|
+
destroy,
|
|
82
|
+
};
|
|
83
|
+
};
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import type { vec2 } from "gl-matrix";
|
|
2
|
+
|
|
3
|
+
export type DepthBuffer = {
|
|
4
|
+
use: () => void;
|
|
5
|
+
resize: (size: vec2) => void;
|
|
6
|
+
read: (pixel: vec2) => readonly [z: number, n: number];
|
|
7
|
+
destroy: () => void;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export const createDepthBuffer = (gl: WebGL2RenderingContext) => {
|
|
11
|
+
const targetTexture = gl.createTexture();
|
|
12
|
+
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
|
|
13
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
|
|
14
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
|
|
15
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
|
|
16
|
+
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
|
|
17
|
+
|
|
18
|
+
const renderbuffer = gl.createRenderbuffer();
|
|
19
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
|
|
20
|
+
|
|
21
|
+
const framebuffer = gl.createFramebuffer();
|
|
22
|
+
|
|
23
|
+
const use = () => gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
24
|
+
|
|
25
|
+
use();
|
|
26
|
+
gl.framebufferTexture2D(
|
|
27
|
+
gl.FRAMEBUFFER,
|
|
28
|
+
gl.COLOR_ATTACHMENT0,
|
|
29
|
+
gl.TEXTURE_2D,
|
|
30
|
+
targetTexture,
|
|
31
|
+
0,
|
|
32
|
+
);
|
|
33
|
+
gl.framebufferRenderbuffer(
|
|
34
|
+
gl.FRAMEBUFFER,
|
|
35
|
+
gl.DEPTH_ATTACHMENT,
|
|
36
|
+
gl.RENDERBUFFER,
|
|
37
|
+
renderbuffer,
|
|
38
|
+
);
|
|
39
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
40
|
+
|
|
41
|
+
let height = 0;
|
|
42
|
+
const resize = ([width = 0, _height = 0]: vec2) => {
|
|
43
|
+
height = _height;
|
|
44
|
+
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
|
|
45
|
+
gl.texImage2D(
|
|
46
|
+
gl.TEXTURE_2D,
|
|
47
|
+
0,
|
|
48
|
+
gl.RGBA,
|
|
49
|
+
width,
|
|
50
|
+
height,
|
|
51
|
+
0,
|
|
52
|
+
gl.RGBA,
|
|
53
|
+
gl.UNSIGNED_BYTE,
|
|
54
|
+
null,
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
gl.bindRenderbuffer(gl.RENDERBUFFER, renderbuffer);
|
|
58
|
+
gl.renderbufferStorage(
|
|
59
|
+
gl.RENDERBUFFER,
|
|
60
|
+
gl.DEPTH_COMPONENT16,
|
|
61
|
+
width,
|
|
62
|
+
height,
|
|
63
|
+
);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const buffer = new Uint8Array(4);
|
|
67
|
+
const read = ([x = 0, y = 0]: vec2) => {
|
|
68
|
+
use();
|
|
69
|
+
gl.readPixels(x, height - y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
|
|
70
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
71
|
+
|
|
72
|
+
const [r = 0, g = 0, b = 0, a = 0] = buffer;
|
|
73
|
+
const zo = (r * 256 + g) / (256 * 256 - 1);
|
|
74
|
+
const z = 2 * zo - 1;
|
|
75
|
+
const n = b * 256 + a;
|
|
76
|
+
|
|
77
|
+
return [z, n] as const;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const destroy = () => {
|
|
81
|
+
gl.deleteTexture(targetTexture);
|
|
82
|
+
gl.deleteFramebuffer(framebuffer);
|
|
83
|
+
gl.deleteRenderbuffer(renderbuffer);
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
use,
|
|
88
|
+
resize,
|
|
89
|
+
read,
|
|
90
|
+
destroy,
|
|
91
|
+
} satisfies DepthBuffer;
|
|
92
|
+
};
|
package/src/elevation.ts
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import type { vec2, vec3 } from "gl-matrix";
|
|
2
|
+
|
|
3
|
+
import type { TileCache } from "./layers/terrain/tile-cache";
|
|
4
|
+
import { createTileDownsampler } from "./layers/terrain/tile-downsampler";
|
|
5
|
+
import { createTileIndexCache } from "./layers/terrain/tile-index-cache";
|
|
6
|
+
import { mercator } from "./math";
|
|
7
|
+
|
|
8
|
+
const defaultZ = 15;
|
|
9
|
+
const size = 256;
|
|
10
|
+
|
|
11
|
+
export type Elevation = {
|
|
12
|
+
get: ([lng, lat]: vec2, z?: number) => number;
|
|
13
|
+
destroy: () => void;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const createElevation = ({
|
|
17
|
+
gl,
|
|
18
|
+
terrainCache,
|
|
19
|
+
}: {
|
|
20
|
+
gl: WebGL2RenderingContext;
|
|
21
|
+
terrainCache: TileCache;
|
|
22
|
+
}) => {
|
|
23
|
+
const tileCache = createTileIndexCache<Uint8Array>({
|
|
24
|
+
max: 1000,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const downsampler = createTileDownsampler(terrainCache);
|
|
28
|
+
|
|
29
|
+
const framebuffer = gl.createFramebuffer();
|
|
30
|
+
if (!framebuffer) throw new Error("Framebuffer creation failed");
|
|
31
|
+
|
|
32
|
+
const downsampleBuffer = ([x = 0, y = 0, z = 0]: vec3) => {
|
|
33
|
+
const tile = downsampler.get([x, y, z]);
|
|
34
|
+
if (!tile) return undefined;
|
|
35
|
+
const { texture, downsample } = tile;
|
|
36
|
+
const k = 2 ** downsample;
|
|
37
|
+
const xyz: vec3 = [Math.floor(x / k), Math.floor(y / k), z - downsample];
|
|
38
|
+
const cached = tileCache.get(xyz);
|
|
39
|
+
if (cached) return { buffer: cached, downsample };
|
|
40
|
+
|
|
41
|
+
const buffer = new Uint8Array(4 * size * size);
|
|
42
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
|
|
43
|
+
texture.attach();
|
|
44
|
+
gl.readPixels(0, 0, size, size, gl.RGBA, gl.UNSIGNED_BYTE, buffer);
|
|
45
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
46
|
+
|
|
47
|
+
tileCache.set(xyz, buffer);
|
|
48
|
+
return { buffer, downsample };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const get = ([lng = 0, lat = 0]: vec2, z = defaultZ) => {
|
|
52
|
+
const k = 2 ** z;
|
|
53
|
+
const p = mercator([lng, lat, 0]).map(_ => _ * k);
|
|
54
|
+
const [x = 0, y = 0] = p.map(_ => Math.floor(_ % k));
|
|
55
|
+
let [px = 0, py = 0] = p.map(_ => _ % 1);
|
|
56
|
+
const downsampled = downsampleBuffer([x, y, z]);
|
|
57
|
+
if (!downsampled) return 0;
|
|
58
|
+
const { buffer, downsample } = downsampled;
|
|
59
|
+
const k2 = 2 ** downsample;
|
|
60
|
+
[px, py] = [((x % k2) + px) / k2, ((y % k2) + py) / k2];
|
|
61
|
+
|
|
62
|
+
const q = 4 * size * Math.floor(py * size) + 4 * Math.floor(px * size);
|
|
63
|
+
const [r = 0, g = 0, b = 0] = buffer.slice(q, q + 4);
|
|
64
|
+
|
|
65
|
+
const value = (r * 65536 + g * 256 + b) / 10 - 10000;
|
|
66
|
+
|
|
67
|
+
return value;
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
const destroy = () => {
|
|
71
|
+
gl.deleteFramebuffer(framebuffer);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
return { get, destroy } satisfies Elevation;
|
|
75
|
+
};
|
package/src/glsl.d.ts
ADDED