@ortho-earth/voxel 0.0.0-stage → 0.4.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/LICENSE +29 -0
- package/README.md +85 -2
- package/dist/grid.d.ts +160 -0
- package/dist/grid.js +455 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.js +289 -0
- package/dist/voxel-draw.d.ts +16 -0
- package/dist/voxel-draw.js +77 -0
- package/dist/voxel-gl.d.ts +184 -0
- package/dist/voxel-gl.js +629 -0
- package/dist/voxel-gpu.d.ts +22 -0
- package/dist/voxel-gpu.js +674 -0
- package/dist/voxel-reader.d.ts +16 -0
- package/dist/voxel-reader.js +16 -0
- package/package.json +42 -5
package/dist/voxel-gl.js
ADDED
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
// 深度の無い時の代わり(DEPTH_GLSL と同じ名前=FS は同じ字で書ける)
|
|
2
|
+
const DEPTH_STUB = `uniform highp float u_sceneLogCoef; uniform highp float u_sceneDepthOn; highp float sceneDepthAt(highp vec2 uv) { return 1.0; }`;
|
|
3
|
+
const VS = `#version 300 es
|
|
4
|
+
precision highp float;
|
|
5
|
+
layout(location = 0) in vec3 a_pos;
|
|
6
|
+
uniform mat4 u_mvp; uniform vec3 u_dims; uniform int u_geo;
|
|
7
|
+
out vec3 v_p;
|
|
8
|
+
void main() { v_p = u_geo == 0 ? a_pos * u_dims : a_pos; gl_Position = u_mvp * vec4(v_p, 1.0); }`;
|
|
9
|
+
const FS = (depthGLSL, pick) => `#version 300 es
|
|
10
|
+
precision highp float; precision highp int; precision highp sampler3D;
|
|
11
|
+
${depthGLSL}
|
|
12
|
+
uniform mat4 u_mvp; uniform vec3 u_dims; uniform vec3 u_eye; uniform vec2 u_vp;
|
|
13
|
+
uniform highp sampler3D u_vol; uniform sampler2D u_cmap;
|
|
14
|
+
uniform int u_mode; uniform int u_comps; uniform int u_maxSteps; uniform int u_geo;
|
|
15
|
+
uniform float u_thr; uniform float u_step; uniform float u_opacity; uniform float u_occl;
|
|
16
|
+
uniform mat3 u_nrm; uniform mat3 u_gradM; uniform vec3 u_light; uniform highp uint u_base;
|
|
17
|
+
uniform vec3 u_lo; uniform vec3 u_hi;
|
|
18
|
+
uniform vec3 u_o; uniform vec3 u_E; uniform vec3 u_N; uniform vec3 u_U; uniform float u_R; uniform float u_rAx;
|
|
19
|
+
uniform vec4 u_geoW; uniform vec2 u_geoH; uniform vec4 u_bs;
|
|
20
|
+
in vec3 v_p;
|
|
21
|
+
out vec4 o;
|
|
22
|
+
// 光線の点 → セル座標(box=そのまま・region=東北上 m → 単位球 → 経緯度・高さ)
|
|
23
|
+
vec3 cellOf(vec3 p) {
|
|
24
|
+
if (u_geo == 0) return p;
|
|
25
|
+
vec3 P = u_o + (u_E * p.x + u_N * p.y + u_U * p.z) / u_R;
|
|
26
|
+
float r = length(P);
|
|
27
|
+
float lat = asin(clamp(P.y / r, -1.0, 1.0));
|
|
28
|
+
if (u_rAx != 1.0) lat = atan(tan(lat) / u_rAx);
|
|
29
|
+
float lon = degrees(atan(P.z, P.x));
|
|
30
|
+
float dl = mod(lon - u_geoW.x, 360.0);
|
|
31
|
+
return vec3(dl / u_geoW.z * u_dims.x, (degrees(lat) - u_geoW.y) / u_geoW.w * u_dims.y, ((r - 1.0) * u_R - u_geoH.x) / (u_geoH.y - u_geoH.x) * u_dims.z);
|
|
32
|
+
}
|
|
33
|
+
bool inRange(vec3 c) { return all(greaterThanEqual(c, u_lo)) && all(lessThan(c, u_hi)); }
|
|
34
|
+
// セルの値(R8=0 は空・1〜255 → 0〜1。RGBA=そのまま・α 0 は空)
|
|
35
|
+
vec4 cellColor(ivec3 c, out bool solid) {
|
|
36
|
+
vec4 s = texelFetch(u_vol, c, 0);
|
|
37
|
+
if (u_comps == 4) { solid = s.a > 0.0; return s; }
|
|
38
|
+
float vn = (s.r * 255.0 - 1.0) / 254.0;
|
|
39
|
+
solid = s.r > 0.0 && vn >= u_thr;
|
|
40
|
+
return texture(u_cmap, vec2(vn, 0.5));
|
|
41
|
+
}
|
|
42
|
+
// 三線形の値(iso)=空は −1
|
|
43
|
+
float valueAt(vec3 c) {
|
|
44
|
+
vec4 s = texture(u_vol, c / u_dims);
|
|
45
|
+
if (u_comps == 4) return s.a > 0.0 ? s.a : -1.0;
|
|
46
|
+
return s.r > 0.5 / 255.0 ? (s.r * 255.0 - 1.0) / 254.0 : -1.0;
|
|
47
|
+
}
|
|
48
|
+
vec4 shade(vec4 col, vec3 ne) { float l = 0.55 + 0.45 * max(0.0, dot(ne, u_light)); float a = u_opacity; return vec4(col.rgb * l * a, a); }
|
|
49
|
+
${pick ? `vec4 encode(highp uint id) { return vec4(float(id & 255u), float((id >> 8) & 255u), float((id >> 16) & 255u), float((id >> 24) & 255u)) / 255.0; }
|
|
50
|
+
highp uint cellId(ivec3 c) { return u_base + uint(c.x + int(u_dims.x) * (c.y + int(u_dims.y) * c.z)); }` : ""}
|
|
51
|
+
void main() {
|
|
52
|
+
vec3 ro = u_eye, rd = normalize(v_p - ro);
|
|
53
|
+
float t0, t1; int axis0 = -1; bool inside = false;
|
|
54
|
+
if (u_geo == 0) { // 箱 [lo, hi](セル空間)との交わり
|
|
55
|
+
vec3 inv = 1.0 / rd;
|
|
56
|
+
vec3 ta = (u_lo - ro) * inv, tb = (u_hi - ro) * inv, tmin = min(ta, tb), tmax = max(ta, tb);
|
|
57
|
+
t0 = max(max(tmin.x, tmin.y), tmin.z); t1 = min(min(tmax.x, tmax.y), tmax.z);
|
|
58
|
+
axis0 = t0 == tmin.x ? 0 : t0 == tmin.y ? 1 : 2;
|
|
59
|
+
inside = t0 < 0.0; t0 = max(t0, 0.0);
|
|
60
|
+
} else { // 殻の裏面まで。入口は殻を包む球
|
|
61
|
+
t1 = length(v_p - ro);
|
|
62
|
+
vec3 oc = ro - u_bs.xyz; float b = dot(oc, rd), c = dot(oc, oc) - u_bs.w * u_bs.w, disc = b * b - c;
|
|
63
|
+
t0 = disc > 0.0 ? max(0.0, -b - sqrt(disc)) : 0.0;
|
|
64
|
+
}
|
|
65
|
+
// シーンの深度=光線を w がそこへ届く所で切る(w は光線に沿って線形)
|
|
66
|
+
if (u_occl > 0.5 && u_sceneDepthOn > 0.5) {
|
|
67
|
+
float s = sceneDepthAt(gl_FragCoord.xy / u_vp);
|
|
68
|
+
if (s < 1.0) {
|
|
69
|
+
float ws = exp2(2.0 * s / u_sceneLogCoef) - 1.0;
|
|
70
|
+
float w0 = dot(vec4(ro, 1.0), vec4(u_mvp[0][3], u_mvp[1][3], u_mvp[2][3], u_mvp[3][3])), dw = dot(rd, vec3(u_mvp[0][3], u_mvp[1][3], u_mvp[2][3]));
|
|
71
|
+
if (dw > 1e-12) t1 = min(t1, (ws - w0) / dw);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (t1 <= t0) discard;
|
|
75
|
+
if (u_mode == 0 && u_geo == 0) { // box の blocks=DDA
|
|
76
|
+
vec3 p = ro + rd * (t0 + 1e-4);
|
|
77
|
+
ivec3 c = clamp(ivec3(floor(p)), ivec3(floor(u_lo)), ivec3(ceil(u_hi)) - 1);
|
|
78
|
+
ivec3 st = ivec3(sign(rd));
|
|
79
|
+
vec3 inv = 1.0 / rd;
|
|
80
|
+
vec3 tMax = (vec3(c) + vec3(greaterThan(rd, vec3(0.0))) - ro) * inv, tDelta = abs(inv);
|
|
81
|
+
int axis = inside ? -1 : axis0;
|
|
82
|
+
for (int i = 0; i < 1024; i++) {
|
|
83
|
+
if (i >= u_maxSteps) break;
|
|
84
|
+
bool solid; vec4 col = cellColor(c, solid);
|
|
85
|
+
if (solid) {
|
|
86
|
+
${pick ? `o = encode(cellId(c)); return;` : `
|
|
87
|
+
vec3 nc = axis < 0 ? -rd : (axis == 0 ? vec3(-float(st.x), 0.0, 0.0) : axis == 1 ? vec3(0.0, -float(st.y), 0.0) : vec3(0.0, 0.0, -float(st.z)));
|
|
88
|
+
o = shade(col, normalize(u_nrm * nc)); return;`}
|
|
89
|
+
}
|
|
90
|
+
if (tMax.x < tMax.y && tMax.x < tMax.z) { if (tMax.x > t1) break; c.x += st.x; tMax.x += tDelta.x; axis = 0; }
|
|
91
|
+
else if (tMax.y < tMax.z) { if (tMax.y > t1) break; c.y += st.y; tMax.y += tDelta.y; axis = 1; }
|
|
92
|
+
else { if (tMax.z > t1) break; c.z += st.z; tMax.z += tDelta.z; axis = 2; }
|
|
93
|
+
if (any(lessThan(c, ivec3(0))) || any(greaterThanEqual(c, ivec3(u_dims)))) break;
|
|
94
|
+
}
|
|
95
|
+
discard;
|
|
96
|
+
}
|
|
97
|
+
// 一定の刻み(box=セル・region=m)。刻みの位相を画素ごとに散らす(縞を消す)
|
|
98
|
+
float jit = fract(sin(dot(gl_FragCoord.xy, vec2(12.9898, 78.233))) * 43758.5453);
|
|
99
|
+
float t = t0 + u_step * (0.25 + 0.5 * jit);
|
|
100
|
+
vec4 acc = vec4(0.0);
|
|
101
|
+
ivec3 prevC = ivec3(-1); float prevV = -1.0, prevT = t0; bool havePrev = false;
|
|
102
|
+
for (int i = 0; i < 4096; i++) {
|
|
103
|
+
if (i >= u_maxSteps || t >= t1) break;
|
|
104
|
+
vec3 p = ro + rd * t, c = cellOf(p);
|
|
105
|
+
if (inRange(c)) {
|
|
106
|
+
if (u_mode == 0) { // region の blocks=最近傍
|
|
107
|
+
ivec3 ci = ivec3(floor(c)); bool solid; vec4 col = cellColor(ci, solid);
|
|
108
|
+
if (solid) {
|
|
109
|
+
${pick ? `o = encode(cellId(ci)); return;` : `
|
|
110
|
+
vec3 nc = -rd;
|
|
111
|
+
if (havePrev) { ivec3 d = ci - prevC; nc = d.x != 0 ? vec3(-float(sign(d.x)), 0.0, 0.0) : d.y != 0 ? vec3(0.0, -float(sign(d.y)), 0.0) : d.z != 0 ? vec3(0.0, 0.0, -float(sign(d.z))) : -rd; }
|
|
112
|
+
o = shade(col, normalize(u_nrm * nc)); return;`}
|
|
113
|
+
}
|
|
114
|
+
prevC = ci; havePrev = true;
|
|
115
|
+
} else if (u_mode == 1) { // volume
|
|
116
|
+
vec4 s = texture(u_vol, c / u_dims);
|
|
117
|
+
vec4 col; float a;
|
|
118
|
+
if (u_comps == 4) { col = s; a = s.a; }
|
|
119
|
+
else { float vn = (s.r * 255.0 - 1.0) / 254.0; if (s.r > 0.5 / 255.0 && vn >= u_thr) { col = texture(u_cmap, vec2(max(vn, 0.0), 0.5)); a = col.a; } else { col = vec4(0.0); a = 0.0; } }
|
|
120
|
+
if (a > 0.0) {
|
|
121
|
+
a = 1.0 - pow(1.0 - min(a * u_opacity, 0.999), u_geo == 0 ? u_step : 1.0);
|
|
122
|
+
acc.rgb += (1.0 - acc.a) * col.rgb * a; acc.a += (1.0 - acc.a) * a;
|
|
123
|
+
${pick ? `if (acc.a >= 0.5) { o = encode(cellId(clamp(ivec3(floor(c)), ivec3(0), ivec3(u_dims) - 1))); return; }` : `if (acc.a > 0.98) break;`}
|
|
124
|
+
}
|
|
125
|
+
} else { // iso=閾を跨いだら二分探索
|
|
126
|
+
float v = valueAt(c);
|
|
127
|
+
if (havePrev && (prevV - u_thr) * (v - u_thr) < 0.0) {
|
|
128
|
+
float a = prevT, b = t, va = prevV;
|
|
129
|
+
for (int k = 0; k < 6; k++) { float m = 0.5 * (a + b); float vm = valueAt(cellOf(ro + rd * m)); if ((vm - u_thr) * (va - u_thr) < 0.0) b = m; else { a = m; va = vm; } }
|
|
130
|
+
vec3 ch = cellOf(ro + rd * b);
|
|
131
|
+
${pick ? `o = encode(cellId(clamp(ivec3(floor(ch)), ivec3(0), ivec3(u_dims) - 1))); return;` : `
|
|
132
|
+
vec3 g = vec3(valueAt(ch + vec3(0.5, 0.0, 0.0)) - valueAt(ch - vec3(0.5, 0.0, 0.0)), valueAt(ch + vec3(0.0, 0.5, 0.0)) - valueAt(ch - vec3(0.0, 0.5, 0.0)), valueAt(ch + vec3(0.0, 0.0, 0.5)) - valueAt(ch - vec3(0.0, 0.0, 0.5)));
|
|
133
|
+
vec3 ne = u_gradM * g; ne = dot(ne, ne) > 1e-12 ? normalize(ne) : -rd;
|
|
134
|
+
if (dot(ne, u_nrm * rd) > 0.0) ne = -ne; // 見ている側へ
|
|
135
|
+
vec4 col = u_comps == 4 ? texture(u_vol, ch / u_dims) : texture(u_cmap, vec2(u_thr, 0.5));
|
|
136
|
+
o = shade(col, ne); return;`}
|
|
137
|
+
}
|
|
138
|
+
prevV = v; prevT = t; havePrev = true;
|
|
139
|
+
}
|
|
140
|
+
} else havePrev = false;
|
|
141
|
+
t += u_step;
|
|
142
|
+
}
|
|
143
|
+
${pick ? `discard;` : `if (u_mode != 1 || acc.a < 1.0 / 255.0) discard; o = acc;`}
|
|
144
|
+
}`;
|
|
145
|
+
let glc = null, host = null, prog = null, pickProg = null, boxVao = null, wantDepth = false, dummyTex = null, max3d = 256;
|
|
146
|
+
let pickFbo = null, pickTex = null, pickW = 0, pickH = 0;
|
|
147
|
+
const layers = new Map(), probes = [];
|
|
148
|
+
const R_SPHERE = 6371000, R_ELL = 6378137, D2R = Math.PI / 180;
|
|
149
|
+
const LIGHT = (() => { const v = [-0.35, 0.45, 0.82], l = Math.hypot(...v); return v.map(x => x / l); })(); // 東北上=北西の上から
|
|
150
|
+
function compile(vs, fs) {
|
|
151
|
+
const gl = glc;
|
|
152
|
+
const mk = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s); if (!gl.getShaderParameter(s, gl.COMPILE_STATUS))
|
|
153
|
+
throw new Error(String(gl.getShaderInfoLog(s))); return s; };
|
|
154
|
+
const p = gl.createProgram();
|
|
155
|
+
gl.attachShader(p, mk(gl.VERTEX_SHADER, vs));
|
|
156
|
+
gl.attachShader(p, mk(gl.FRAGMENT_SHADER, fs));
|
|
157
|
+
gl.linkProgram(p);
|
|
158
|
+
if (!gl.getProgramParameter(p, gl.LINK_STATUS))
|
|
159
|
+
throw new Error(String(gl.getProgramInfoLog(p)));
|
|
160
|
+
const u = {};
|
|
161
|
+
for (let i = 0, n = gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS); i < n; i++) {
|
|
162
|
+
const nm = gl.getActiveUniform(p, i).name;
|
|
163
|
+
u[nm] = gl.getUniformLocation(p, nm);
|
|
164
|
+
}
|
|
165
|
+
return { p, u };
|
|
166
|
+
}
|
|
167
|
+
// 単位立方体の 12 枚の三角形(外向きの CCW)=cullFace(FRONT) で裏面だけ描く
|
|
168
|
+
export const BOX = (() => {
|
|
169
|
+
const f = [[0, 1, 2, 3], [5, 4, 7, 6], [4, 0, 3, 7], [1, 5, 6, 2], [3, 2, 6, 7], [4, 5, 1, 0]]; // -z +z -x +x +y -y(各面を外から見て CCW)
|
|
170
|
+
const v = [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0], [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]];
|
|
171
|
+
const out = [];
|
|
172
|
+
for (const [a, b, c, d] of f)
|
|
173
|
+
for (const i of [a, c, b, a, d, c])
|
|
174
|
+
out.push(...v[i]);
|
|
175
|
+
return new Float32Array(out);
|
|
176
|
+
})();
|
|
177
|
+
export function init(canvas, opts = {}, h = null) {
|
|
178
|
+
host = h;
|
|
179
|
+
layers.clear();
|
|
180
|
+
probes.length = 0;
|
|
181
|
+
pickFbo = null;
|
|
182
|
+
pickTex = null;
|
|
183
|
+
pickW = 0;
|
|
184
|
+
pickH = 0;
|
|
185
|
+
dummyTex = null; // 同じ名前で立て直したオーバーレイ=モジュールの実体は使い回し(前の文脈の物は捨てる)
|
|
186
|
+
const gl = glc = canvas.getContext("webgl2", { premultipliedAlpha: true, antialias: false, alpha: true, depth: false });
|
|
187
|
+
if (!gl)
|
|
188
|
+
throw new Error("WebGL2 is not available (voxel overlay)");
|
|
189
|
+
wantDepth = opts.depth !== false && !!h?.depthGLSL;
|
|
190
|
+
const dg = wantDepth && h?.depthGLSL ? h.depthGLSL : DEPTH_STUB;
|
|
191
|
+
prog = compile(VS, FS(dg, false));
|
|
192
|
+
try {
|
|
193
|
+
pickProg = compile(VS, FS(dg, true));
|
|
194
|
+
}
|
|
195
|
+
catch (e) {
|
|
196
|
+
pickProg = null;
|
|
197
|
+
console.warn("[voxel] pick pass unavailable", e?.message);
|
|
198
|
+
}
|
|
199
|
+
max3d = gl.getParameter(gl.MAX_3D_TEXTURE_SIZE) || 256;
|
|
200
|
+
boxVao = gl.createVertexArray();
|
|
201
|
+
gl.bindVertexArray(boxVao);
|
|
202
|
+
const vb = gl.createBuffer();
|
|
203
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vb);
|
|
204
|
+
gl.bufferData(gl.ARRAY_BUFFER, BOX, gl.STATIC_DRAW);
|
|
205
|
+
gl.enableVertexAttribArray(0);
|
|
206
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
207
|
+
gl.bindVertexArray(null);
|
|
208
|
+
return wantDepth ? { depth: true } : undefined;
|
|
209
|
+
}
|
|
210
|
+
const unit = (lon, lat, rAx) => { const a = lon * D2R; let b = lat * D2R; if (rAx !== 1)
|
|
211
|
+
b = Math.atan(rAx * Math.tan(b)); const cb = Math.cos(b); return [cb * Math.cos(a), Math.sin(b), cb * Math.sin(a)]; };
|
|
212
|
+
/** 錨の東北上 m → 単位球(列=東・北・上×1/R) */
|
|
213
|
+
export function enuBasis(lon, lat, rAx = 1) {
|
|
214
|
+
const u = unit(lon, lat, rAx), cl = Math.hypot(u[0], u[2]), k = 1 / (rAx === 1 ? R_SPHERE : R_ELL);
|
|
215
|
+
const E = cl < 1e-12 ? [1, 0, 0] : [-u[2] / cl, 0, u[0] / cl], N = [E[1] * u[2] - E[2] * u[1], E[2] * u[0] - E[0] * u[2], E[0] * u[1] - E[1] * u[0]];
|
|
216
|
+
return { E: E.map(v => v * k), N: N.map(v => v * k), U: u.map(v => v * k), u };
|
|
217
|
+
}
|
|
218
|
+
function inv3(m) {
|
|
219
|
+
const [a, b, c, d, e, f, g, h, i] = Array.from(m, Number);
|
|
220
|
+
const A = e * i - f * h, B = -(d * i - f * g), C = d * h - e * g, det = a * A + b * B + c * C, k = 1 / det;
|
|
221
|
+
return Float64Array.from([A * k, -(b * i - c * h) * k, (b * f - c * e) * k, B * k, (a * i - c * g) * k, -(a * f - c * d) * k, C * k, -(a * h - b * g) * k, (a * e - b * d) * k]);
|
|
222
|
+
}
|
|
223
|
+
/** 法線の行列(セルの軸 → 東北上の単位ベクトル・列優先)と勾配の行列(A⁻ᵀ・列優先) */
|
|
224
|
+
export function normalMats(A, Ainv) {
|
|
225
|
+
const nrm = new Float32Array(9), gradM = new Float32Array(9);
|
|
226
|
+
for (let c = 0; c < 3; c++) {
|
|
227
|
+
const l = Math.hypot(A[c], A[3 + c], A[6 + c]) || 1;
|
|
228
|
+
nrm[c * 3] = A[c] / l;
|
|
229
|
+
nrm[c * 3 + 1] = A[3 + c] / l;
|
|
230
|
+
nrm[c * 3 + 2] = A[6 + c] / l;
|
|
231
|
+
}
|
|
232
|
+
for (let c = 0; c < 3; c++)
|
|
233
|
+
for (let r = 0; r < 3; r++)
|
|
234
|
+
gradM[c * 3 + r] = Ainv[c * 3 + r]; // A⁻ᵀ の列 c=A⁻¹ の行 c
|
|
235
|
+
return { nrm, gradM };
|
|
236
|
+
}
|
|
237
|
+
const upCmap = (t, cmap) => { const gl = glc; gl.bindTexture(gl.TEXTURE_2D, t); gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, cmap); gl.bindTexture(gl.TEXTURE_2D, null); };
|
|
238
|
+
function build(d) {
|
|
239
|
+
const gl = glc;
|
|
240
|
+
const [nx, ny, nz] = d.dims;
|
|
241
|
+
if (nx > max3d || ny > max3d || nz > max3d)
|
|
242
|
+
throw new Error(`voxel: dims ${d.dims.join("×")} exceed MAX_3D_TEXTURE_SIZE ${max3d}`);
|
|
243
|
+
const mk = (data) => {
|
|
244
|
+
const tex = gl.createTexture();
|
|
245
|
+
gl.bindTexture(gl.TEXTURE_3D, tex);
|
|
246
|
+
gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
|
|
247
|
+
if (d.comps === 4)
|
|
248
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.RGBA8, nx, ny, nz, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
249
|
+
else
|
|
250
|
+
gl.texImage3D(gl.TEXTURE_3D, 0, gl.R8, nx, ny, nz, 0, gl.RED, gl.UNSIGNED_BYTE, data);
|
|
251
|
+
for (const [p, v] of [[gl.TEXTURE_MIN_FILTER, gl.LINEAR], [gl.TEXTURE_MAG_FILTER, gl.LINEAR], [gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE], [gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE], [gl.TEXTURE_WRAP_R, gl.CLAMP_TO_EDGE]])
|
|
252
|
+
gl.texParameteri(gl.TEXTURE_3D, p, v);
|
|
253
|
+
gl.bindTexture(gl.TEXTURE_3D, null);
|
|
254
|
+
return tex;
|
|
255
|
+
};
|
|
256
|
+
const texs = (d.frames?.length ? d.frames : [d.tex]).map(mk), frame = Math.min(texs.length - 1, Math.max(0, d.frame ?? 0)), tex = texs[frame];
|
|
257
|
+
const cmap = gl.createTexture();
|
|
258
|
+
gl.bindTexture(gl.TEXTURE_2D, cmap);
|
|
259
|
+
for (const [p, v] of [[gl.TEXTURE_MIN_FILTER, gl.LINEAR], [gl.TEXTURE_MAG_FILTER, gl.LINEAR], [gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE], [gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE]])
|
|
260
|
+
gl.texParameteri(gl.TEXTURE_2D, p, v);
|
|
261
|
+
upCmap(cmap, d.cmap);
|
|
262
|
+
const A = Float64Array.from(d.A), Ainv = inv3(A), { nrm, gradM } = normalMats(A, Ainv);
|
|
263
|
+
let vao = null, vb = null, nVerts = 36;
|
|
264
|
+
const geo = d.shape === "region" ? d.geo ?? null : null;
|
|
265
|
+
if (geo) { // region=殻(東北上 m)
|
|
266
|
+
vao = gl.createVertexArray();
|
|
267
|
+
gl.bindVertexArray(vao);
|
|
268
|
+
vb = gl.createBuffer();
|
|
269
|
+
gl.bindBuffer(gl.ARRAY_BUFFER, vb);
|
|
270
|
+
gl.bufferData(gl.ARRAY_BUFFER, geo.shell, gl.STATIC_DRAW);
|
|
271
|
+
gl.enableVertexAttribArray(0);
|
|
272
|
+
gl.vertexAttribPointer(0, 3, gl.FLOAT, false, 0, 0);
|
|
273
|
+
gl.bindVertexArray(null);
|
|
274
|
+
nVerts = geo.shell.length / 3;
|
|
275
|
+
}
|
|
276
|
+
return { shape: geo ? "region" : "box", dims: [nx, ny, nz], comps: d.comps, n: nx * ny * nz, anchor: d.anchor, alt: d.alt || "relative", A, Ainv, radius: d.radius || 0, mode: d.mode, thr: d.thr || 0, step: d.step || 0.5, opacity: d.opacity ?? 1, occl: d.depthTest !== false, clip: d.clip ?? null, properties: d.properties ?? null, range: d.range, tex, cmap, on: d.on !== false, base: 0, dist: 0, nrm, gradM, geo, vao, vb, nVerts, layer: d.layer ?? null, off: d.off ?? null, own: d.own ?? null, full: d.full ?? null, vmax: d.vmax ?? 255, texs, frame };
|
|
277
|
+
}
|
|
278
|
+
const drop = (L) => { if (!L)
|
|
279
|
+
return; const gl = glc; for (const t of L.texs)
|
|
280
|
+
gl.deleteTexture(t); gl.deleteTexture(L.cmap); if (L.vb)
|
|
281
|
+
gl.deleteBuffer(L.vb); if (L.vao)
|
|
282
|
+
gl.deleteVertexArray(L.vao); };
|
|
283
|
+
function rebase() { let b = 1; for (const L of layers.values()) {
|
|
284
|
+
L.base = b;
|
|
285
|
+
b += L.n;
|
|
286
|
+
} }
|
|
287
|
+
export function message(d) {
|
|
288
|
+
if (d.type === "probe") {
|
|
289
|
+
probes.push(d.id);
|
|
290
|
+
host?.requestDraw?.();
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
if (!glc)
|
|
294
|
+
return;
|
|
295
|
+
if (d.type === "layer") {
|
|
296
|
+
drop(layers.get(d.q));
|
|
297
|
+
layers.delete(d.q);
|
|
298
|
+
try {
|
|
299
|
+
layers.set(d.q, build(d));
|
|
300
|
+
}
|
|
301
|
+
catch (e) {
|
|
302
|
+
host?.post?.({ type: "error", q: d.q, error: String(e?.message || e) });
|
|
303
|
+
}
|
|
304
|
+
rebase();
|
|
305
|
+
host?.requestDraw?.();
|
|
306
|
+
}
|
|
307
|
+
else if (d.type === "style") {
|
|
308
|
+
const L = layers.get(d.q);
|
|
309
|
+
if (!L)
|
|
310
|
+
return;
|
|
311
|
+
if (d.frame != null && L.texs.length > 1) {
|
|
312
|
+
L.frame = Math.min(L.texs.length - 1, Math.max(0, d.frame));
|
|
313
|
+
L.tex = L.texs[L.frame];
|
|
314
|
+
}
|
|
315
|
+
if (d.mode != null)
|
|
316
|
+
L.mode = d.mode;
|
|
317
|
+
if (d.thr != null)
|
|
318
|
+
L.thr = d.thr;
|
|
319
|
+
if (d.step != null)
|
|
320
|
+
L.step = d.step;
|
|
321
|
+
if (d.opacity != null)
|
|
322
|
+
L.opacity = d.opacity;
|
|
323
|
+
if (d.clip !== undefined)
|
|
324
|
+
L.clip = d.clip ?? null;
|
|
325
|
+
if (d.depthTest != null)
|
|
326
|
+
L.occl = d.depthTest;
|
|
327
|
+
if (d.properties !== undefined)
|
|
328
|
+
L.properties = d.properties ?? null;
|
|
329
|
+
if (d.cmap)
|
|
330
|
+
upCmap(L.cmap, d.cmap);
|
|
331
|
+
host?.requestDraw?.();
|
|
332
|
+
}
|
|
333
|
+
else if (d.type === "remove") {
|
|
334
|
+
drop(layers.get(d.q));
|
|
335
|
+
layers.delete(d.q);
|
|
336
|
+
rebase();
|
|
337
|
+
host?.requestDraw?.();
|
|
338
|
+
}
|
|
339
|
+
else if (d.type === "vis") {
|
|
340
|
+
const L = layers.get(d.q);
|
|
341
|
+
if (L)
|
|
342
|
+
L.on = !!d.on;
|
|
343
|
+
host?.requestDraw?.();
|
|
344
|
+
}
|
|
345
|
+
else if (d.type === "clear") {
|
|
346
|
+
layers.forEach(drop);
|
|
347
|
+
layers.clear();
|
|
348
|
+
rebase();
|
|
349
|
+
host?.requestDraw?.();
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
// 層の mvp(box=セル空間 → clip=mvp × [E N U o] × A・region=東北上 m → clip)と、目の位置(同じ空間)。倍精度で掛けてから f32
|
|
353
|
+
const mvpL = new Float64Array(16), mvpC = new Float32Array(16), eyeC = new Float32Array(3), geoU = { o: new Float32Array(3), E: new Float32Array(3), N: new Float32Array(3), U: new Float32Array(3), R: 0, rAx: 1 };
|
|
354
|
+
function place(L, s, api) {
|
|
355
|
+
const rAx = api?.rAx || 1, [lon, lat, alt] = L.anchor, B = enuBasis(lon, lat, rAx), R = rAx === 1 ? R_SPHERE : R_ELL, k = 1 / R;
|
|
356
|
+
const ls = L.alt !== "absolute" ? (api?.liftScale || 0) : 0, r = 1 + (ls > 0 ? (api.elevM(lon, lat) || 0) * ls : 0) + (alt || 0) * k;
|
|
357
|
+
const o = [B.u[0] * r, B.u[1] * r, B.u[2] * r], M = s.mvp, cols = [B.E, B.N, B.U];
|
|
358
|
+
for (let j = 0; j < 3; j++)
|
|
359
|
+
for (let row = 0; row < 4; row++)
|
|
360
|
+
mvpL[j * 4 + row] = M[row] * cols[j][0] + M[4 + row] * cols[j][1] + M[8 + row] * cols[j][2];
|
|
361
|
+
for (let row = 0; row < 4; row++)
|
|
362
|
+
mvpL[12 + row] = M[row] * o[0] + M[4 + row] * o[1] + M[8 + row] * o[2] + M[12 + row];
|
|
363
|
+
const E = s.eye;
|
|
364
|
+
if (!E)
|
|
365
|
+
return false;
|
|
366
|
+
const d = [E[0] - o[0], E[1] - o[1], E[2] - o[2]], enu = [0, 0, 0];
|
|
367
|
+
for (let j = 0; j < 3; j++)
|
|
368
|
+
enu[j] = (d[0] * cols[j][0] + d[1] * cols[j][1] + d[2] * cols[j][2]) * R * R; // 列は 1/R 倍の単位ベクトル=内積 ×R で単位球の長さ・×R で m
|
|
369
|
+
if (L.geo) { // region=東北上 m のまま
|
|
370
|
+
for (let i = 0; i < 16; i++)
|
|
371
|
+
mvpC[i] = mvpL[i];
|
|
372
|
+
for (let i = 0; i < 3; i++) {
|
|
373
|
+
eyeC[i] = enu[i];
|
|
374
|
+
geoU.o[i] = o[i];
|
|
375
|
+
geoU.E[i] = cols[0][i] * R;
|
|
376
|
+
geoU.N[i] = cols[1][i] * R;
|
|
377
|
+
geoU.U[i] = cols[2][i] * R;
|
|
378
|
+
}
|
|
379
|
+
geoU.R = R;
|
|
380
|
+
geoU.rAx = rAx;
|
|
381
|
+
}
|
|
382
|
+
else { // box=× A(セル → 東北上 m・行優先)=列 c の clip ベクトル=Σ_j mvpL 列 j × A[j][c]
|
|
383
|
+
const A = L.A;
|
|
384
|
+
for (let c = 0; c < 3; c++)
|
|
385
|
+
for (let row = 0; row < 4; row++)
|
|
386
|
+
mvpC[c * 4 + row] = mvpL[row] * A[c] + mvpL[4 + row] * A[3 + c] + mvpL[8 + row] * A[6 + c];
|
|
387
|
+
for (let row = 0; row < 4; row++)
|
|
388
|
+
mvpC[12 + row] = mvpL[12 + row];
|
|
389
|
+
const Ai = L.Ainv, off = L.off;
|
|
390
|
+
for (let i = 0; i < 3; i++)
|
|
391
|
+
eyeC[i] = Ai[i * 3] * enu[0] + Ai[i * 3 + 1] * enu[1] + Ai[i * 3 + 2] * enu[2];
|
|
392
|
+
if (off)
|
|
393
|
+
for (let row = 0; row < 4; row++)
|
|
394
|
+
mvpC[12 + row] += mvpC[row] * off[0] + mvpC[4 + row] * off[1] + mvpC[8 + row] * off[2]; // ブリック=セルを off だけずらす(A·off)
|
|
395
|
+
if (off)
|
|
396
|
+
for (let i = 0; i < 3; i++)
|
|
397
|
+
eyeC[i] -= off[i];
|
|
398
|
+
}
|
|
399
|
+
L.dist = centerDist(L, enu);
|
|
400
|
+
const rr = L.radius * k; // 地平線の向こう(錨の半径ぶんの余裕)
|
|
401
|
+
const behind = o[0] * d[0] + o[1] * d[1] + o[2] * d[2] < -rr * Math.hypot(...o) - 1e-9;
|
|
402
|
+
return !behind;
|
|
403
|
+
}
|
|
404
|
+
function bindDepth(P, api) {
|
|
405
|
+
const gl = glc;
|
|
406
|
+
if (!wantDepth)
|
|
407
|
+
return;
|
|
408
|
+
if (api?.depth) {
|
|
409
|
+
api.depth.bind(gl, P.p, 3);
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (P.u.u_sceneDepthOn)
|
|
413
|
+
gl.uniform1f(P.u.u_sceneDepthOn, 0);
|
|
414
|
+
if (P.u.u_sceneDepth) {
|
|
415
|
+
if (!dummyTex) {
|
|
416
|
+
dummyTex = gl.createTexture();
|
|
417
|
+
gl.bindTexture(gl.TEXTURE_2D, dummyTex);
|
|
418
|
+
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA8, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(4));
|
|
419
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
420
|
+
}
|
|
421
|
+
gl.activeTexture(gl.TEXTURE3);
|
|
422
|
+
gl.bindTexture(gl.TEXTURE_2D, dummyTex);
|
|
423
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
424
|
+
gl.uniform1i(P.u.u_sceneDepth, 3);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
/** 刻み(box=セル・region=m)と最大の歩数(光線の空間で) */
|
|
428
|
+
export function stepsOf(L) {
|
|
429
|
+
if (L.geo) {
|
|
430
|
+
const cm = Math.min(...L.geo.cellM), step = (L.mode === 0 ? Math.min(L.step, 0.25) : L.step) * cm;
|
|
431
|
+
return { step, maxSteps: Math.min(4096, Math.ceil(2 * L.geo.bs[3] / step) + 2) };
|
|
432
|
+
}
|
|
433
|
+
if (L.mode === 0)
|
|
434
|
+
return { step: L.step, maxSteps: L.dims[0] + L.dims[1] + L.dims[2] + 2 };
|
|
435
|
+
return { step: L.step, maxSteps: Math.min(4096, Math.ceil(Math.hypot(L.dims[0], L.dims[1], L.dims[2]) / L.step) + 2) };
|
|
436
|
+
}
|
|
437
|
+
/** 断面 → セル空間の範囲 */
|
|
438
|
+
export const clipBounds = (dims, clip) => ({ lo: [0, 1, 2].map(i => (clip ? clip[i * 2] : 0) * dims[i]), hi: [0, 1, 2].map(i => (clip ? clip[i * 2 + 1] : 1) * dims[i]) });
|
|
439
|
+
/** 描く範囲(テクスチャのセル座標)=断面(親の割合)と持ち分の交わり。空なら null(そのブリックは描かない) */
|
|
440
|
+
export function drawBounds(L) {
|
|
441
|
+
if (!L.full || !L.off || !L.own)
|
|
442
|
+
return clipBounds(L.dims, L.clip);
|
|
443
|
+
const c = clipBounds(L.full, L.clip), lo = [0, 1, 2].map(i => Math.max(L.own[i], c.lo[i] - L.off[i])), hi = [0, 1, 2].map(i => Math.min(L.own[3 + i], c.hi[i] - L.off[i]));
|
|
444
|
+
return lo.every((v, i) => v < hi[i]) ? { lo, hi } : null;
|
|
445
|
+
}
|
|
446
|
+
/** 閾より下しか無い層(ブリック)=描かない(vmax=R8 の 0〜255。RGBA は α 0 の時だけ) */
|
|
447
|
+
export const isEmpty = (L) => L.vmax === 0 || (L.comps === 1 && (L.vmax - 1) / 254 < L.thr);
|
|
448
|
+
/** 目(東北上 m)から層の中心まで=奥から描く順(同じ大きさの箱が並ぶブリックは中心の遠い順で正しく重なる) */
|
|
449
|
+
export function centerDist(L, enu) {
|
|
450
|
+
let c;
|
|
451
|
+
if (L.geo)
|
|
452
|
+
c = [L.geo.bs[0], L.geo.bs[1], L.geo.bs[2]];
|
|
453
|
+
else {
|
|
454
|
+
const o = L.off ?? [0, 0, 0], m = [0, 1, 2].map(i => o[i] + L.dims[i] / 2), A = L.A;
|
|
455
|
+
c = [0, 1, 2].map(r => A[r * 3] * m[0] + A[r * 3 + 1] * m[1] + A[r * 3 + 2] * m[2]);
|
|
456
|
+
}
|
|
457
|
+
return Math.hypot(enu[0] - c[0], enu[1] - c[1], enu[2] - c[2]);
|
|
458
|
+
}
|
|
459
|
+
/** 当たり(層の中の番号)→ 親の層の id・番号・セル(ブリックは off と full で戻す) */
|
|
460
|
+
export function pickedCell(q, L, index) {
|
|
461
|
+
const [nx, ny] = L.dims, i = index % nx, j = Math.floor(index / nx) % ny, k = Math.floor(index / (nx * ny));
|
|
462
|
+
if (!L.off || !L.full)
|
|
463
|
+
return { layer: L.layer ?? q, index, cell: [i, j, k] };
|
|
464
|
+
const cell = [i + L.off[0], j + L.off[1], k + L.off[2]];
|
|
465
|
+
return { layer: L.layer ?? q, index: cell[0] + L.full[0] * (cell[1] + L.full[1] * cell[2]), cell };
|
|
466
|
+
}
|
|
467
|
+
function drawAll(pick, s, api, w, h) {
|
|
468
|
+
const gl = glc;
|
|
469
|
+
const P = (pick ? pickProg : prog);
|
|
470
|
+
gl.disable(gl.DEPTH_TEST);
|
|
471
|
+
gl.enable(gl.CULL_FACE);
|
|
472
|
+
gl.cullFace(gl.FRONT);
|
|
473
|
+
if (pick)
|
|
474
|
+
gl.disable(gl.BLEND);
|
|
475
|
+
else {
|
|
476
|
+
gl.enable(gl.BLEND);
|
|
477
|
+
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
|
|
478
|
+
}
|
|
479
|
+
gl.useProgram(P.p);
|
|
480
|
+
bindDepth(P, api);
|
|
481
|
+
gl.uniform2f(P.u.u_vp, w, h);
|
|
482
|
+
gl.uniform1i(P.u.u_vol, 0);
|
|
483
|
+
gl.uniform1i(P.u.u_cmap, 1);
|
|
484
|
+
gl.uniform3f(P.u.u_light, LIGHT[0], LIGHT[1], LIGHT[2]);
|
|
485
|
+
const vis = [];
|
|
486
|
+
for (const L of layers.values())
|
|
487
|
+
if (L.on && L.n && !isEmpty(L) && drawBounds(L) && place(L, s, api))
|
|
488
|
+
vis.push(L);
|
|
489
|
+
vis.sort((a, b) => b.dist - a.dist); // 層は奥から
|
|
490
|
+
for (const L of vis) {
|
|
491
|
+
place(L, s, api);
|
|
492
|
+
const { step, maxSteps } = stepsOf(L), { lo, hi } = drawBounds(L);
|
|
493
|
+
gl.uniformMatrix4fv(P.u.u_mvp, false, mvpC);
|
|
494
|
+
gl.uniform3f(P.u.u_dims, L.dims[0], L.dims[1], L.dims[2]);
|
|
495
|
+
gl.uniform3fv(P.u.u_eye, eyeC);
|
|
496
|
+
gl.uniformMatrix3fv(P.u.u_nrm, false, L.nrm);
|
|
497
|
+
gl.uniformMatrix3fv(P.u.u_gradM, false, L.gradM);
|
|
498
|
+
gl.uniform1i(P.u.u_mode, L.mode);
|
|
499
|
+
gl.uniform1i(P.u.u_comps, L.comps);
|
|
500
|
+
gl.uniform1i(P.u.u_geo, L.geo ? 1 : 0);
|
|
501
|
+
gl.uniform1i(P.u.u_maxSteps, maxSteps);
|
|
502
|
+
gl.uniform1f(P.u.u_thr, L.thr);
|
|
503
|
+
gl.uniform1f(P.u.u_step, step);
|
|
504
|
+
gl.uniform1f(P.u.u_opacity, L.opacity);
|
|
505
|
+
gl.uniform1f(P.u.u_occl, L.occl ? 1 : 0);
|
|
506
|
+
gl.uniform3f(P.u.u_lo, lo[0], lo[1], lo[2]);
|
|
507
|
+
gl.uniform3f(P.u.u_hi, hi[0], hi[1], hi[2]);
|
|
508
|
+
gl.uniform1ui(P.u.u_base, L.base);
|
|
509
|
+
if (L.geo) {
|
|
510
|
+
gl.uniform3fv(P.u.u_o, geoU.o);
|
|
511
|
+
gl.uniform3fv(P.u.u_E, geoU.E);
|
|
512
|
+
gl.uniform3fv(P.u.u_N, geoU.N);
|
|
513
|
+
gl.uniform3fv(P.u.u_U, geoU.U);
|
|
514
|
+
gl.uniform1f(P.u.u_R, geoU.R);
|
|
515
|
+
gl.uniform1f(P.u.u_rAx, geoU.rAx);
|
|
516
|
+
gl.uniform4f(P.u.u_geoW, L.geo.west, L.geo.south, L.geo.spanLon, L.geo.spanLat);
|
|
517
|
+
gl.uniform2f(P.u.u_geoH, L.geo.h0, L.geo.h1);
|
|
518
|
+
gl.uniform4f(P.u.u_bs, L.geo.bs[0], L.geo.bs[1], L.geo.bs[2], L.geo.bs[3]);
|
|
519
|
+
}
|
|
520
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
521
|
+
gl.bindTexture(gl.TEXTURE_3D, L.tex);
|
|
522
|
+
gl.activeTexture(gl.TEXTURE1);
|
|
523
|
+
gl.bindTexture(gl.TEXTURE_2D, L.cmap);
|
|
524
|
+
gl.bindVertexArray(L.vao ?? boxVao);
|
|
525
|
+
gl.drawArrays(gl.TRIANGLES, 0, L.nVerts);
|
|
526
|
+
}
|
|
527
|
+
gl.activeTexture(gl.TEXTURE0);
|
|
528
|
+
gl.bindVertexArray(null);
|
|
529
|
+
gl.disable(gl.CULL_FACE);
|
|
530
|
+
}
|
|
531
|
+
function answerProbes(w, h) {
|
|
532
|
+
if (!probes.length)
|
|
533
|
+
return;
|
|
534
|
+
const gl = glc;
|
|
535
|
+
const ids = probes.splice(0);
|
|
536
|
+
const ls = [...layers].map(([q, L]) => ({ q, on: L.on, n: L.n, dims: L.dims, mode: L.mode, shape: L.shape, dist: +L.dist.toFixed(4), anchor: L.anchor, alt: L.alt }));
|
|
537
|
+
try {
|
|
538
|
+
const data = new Uint8Array(w * h * 4);
|
|
539
|
+
gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, data);
|
|
540
|
+
for (const id of ids)
|
|
541
|
+
host?.post?.({ type: "pixels", id, w, h, flipY: true, layers: ls, data: id === ids[ids.length - 1] ? data : data.slice() });
|
|
542
|
+
}
|
|
543
|
+
catch (e) {
|
|
544
|
+
for (const id of ids)
|
|
545
|
+
host?.post?.({ type: "pixels", id, error: String(e?.message || e) });
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
export function frame(cam, s, { w, h }, api) {
|
|
549
|
+
const gl = glc;
|
|
550
|
+
if (!gl)
|
|
551
|
+
return false;
|
|
552
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
553
|
+
gl.viewport(0, 0, w, h);
|
|
554
|
+
gl.clearColor(0, 0, 0, 0);
|
|
555
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
556
|
+
if (layers.size)
|
|
557
|
+
drawAll(false, s, api, w, h);
|
|
558
|
+
answerProbes(w, h);
|
|
559
|
+
return false;
|
|
560
|
+
}
|
|
561
|
+
const decodeId = (r, g, b, a) => (r | (g << 8) | (b << 16) | (a << 24)) >>> 0;
|
|
562
|
+
/** 当たりの番号 → セル・経緯度(セルの中心=box は A で・region は経緯度の枠で) */
|
|
563
|
+
export function hitOf(L, index) {
|
|
564
|
+
const [nx, ny] = L.dims, i = index % nx, j = Math.floor(index / nx) % ny, k = Math.floor(index / (nx * ny)), cc = [i + 0.5, j + 0.5, k + 0.5];
|
|
565
|
+
if (L.geo)
|
|
566
|
+
return { cell: [i, j, k], lngLat: [((L.geo.west + L.geo.spanLon * cc[0] / nx + 180) % 360 + 360) % 360 - 180, L.geo.south + L.geo.spanLat * cc[1] / ny] };
|
|
567
|
+
if (L.off)
|
|
568
|
+
for (let a = 0; a < 3; a++)
|
|
569
|
+
cc[a] += L.off[a]; // ブリック=親のセル座標へ
|
|
570
|
+
const A = L.A, e = A[0] * cc[0] + A[1] * cc[1] + A[2] * cc[2], nn = A[3] * cc[0] + A[4] * cc[1] + A[5] * cc[2], a = L.anchor;
|
|
571
|
+
return { cell: [i, j, k], lngLat: [a[0] + e / (Math.cos(a[1] * D2R) * D2R * R_SPHERE), a[1] + nn / (D2R * R_SPHERE)] };
|
|
572
|
+
}
|
|
573
|
+
export function pick(x, y, r, ctx) {
|
|
574
|
+
const gl = glc;
|
|
575
|
+
if (!gl || !pickProg || !layers.size || !ctx?.s)
|
|
576
|
+
return null;
|
|
577
|
+
const { w, h } = ctx.size;
|
|
578
|
+
if (pickW !== w || pickH !== h || !pickFbo) {
|
|
579
|
+
if (pickTex)
|
|
580
|
+
gl.deleteTexture(pickTex);
|
|
581
|
+
if (pickFbo)
|
|
582
|
+
gl.deleteFramebuffer(pickFbo);
|
|
583
|
+
pickTex = gl.createTexture();
|
|
584
|
+
gl.bindTexture(gl.TEXTURE_2D, pickTex);
|
|
585
|
+
gl.texStorage2D(gl.TEXTURE_2D, 1, gl.RGBA8, w, h);
|
|
586
|
+
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
587
|
+
pickFbo = gl.createFramebuffer();
|
|
588
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, pickFbo);
|
|
589
|
+
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, pickTex, 0);
|
|
590
|
+
pickW = w;
|
|
591
|
+
pickH = h;
|
|
592
|
+
}
|
|
593
|
+
else
|
|
594
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, pickFbo);
|
|
595
|
+
const rr = Math.max(0, Math.round(r)), side = rr * 2 + 1, x0 = Math.round(x) - rr, y0 = (h - 1 - Math.round(y)) - rr;
|
|
596
|
+
const cx0 = Math.max(0, x0), cy0 = Math.max(0, y0), cx1 = Math.min(w, x0 + side), cy1 = Math.min(h, y0 + side);
|
|
597
|
+
gl.viewport(0, 0, w, h);
|
|
598
|
+
gl.enable(gl.SCISSOR_TEST);
|
|
599
|
+
gl.scissor(cx0, cy0, Math.max(0, cx1 - cx0), Math.max(0, cy1 - cy0)); // 光線を辿るのは読む画素だけ(全画面の当たりパスは SwiftShader で秒単位)
|
|
600
|
+
gl.clearColor(0, 0, 0, 0);
|
|
601
|
+
gl.clear(gl.COLOR_BUFFER_BIT);
|
|
602
|
+
if (cx1 > cx0 && cy1 > cy0)
|
|
603
|
+
drawAll(true, ctx.s, ctx.api, w, h);
|
|
604
|
+
gl.disable(gl.SCISSOR_TEST);
|
|
605
|
+
let best = null;
|
|
606
|
+
if (cx1 > cx0 && cy1 > cy0) {
|
|
607
|
+
const sub = new Uint8Array((cx1 - cx0) * (cy1 - cy0) * 4);
|
|
608
|
+
gl.readPixels(cx0, cy0, cx1 - cx0, cy1 - cy0, gl.RGBA, gl.UNSIGNED_BYTE, sub);
|
|
609
|
+
for (let j = 0; j < cy1 - cy0; j++)
|
|
610
|
+
for (let i = 0; i < cx1 - cx0; i++) {
|
|
611
|
+
const k = (j * (cx1 - cx0) + i) * 4, id = decodeId(sub[k], sub[k + 1], sub[k + 2], sub[k + 3]);
|
|
612
|
+
if (!id)
|
|
613
|
+
continue;
|
|
614
|
+
const dd = Math.hypot(cx0 + i - (x0 + rr), cy0 + j - (y0 + rr));
|
|
615
|
+
if (!best || dd < best.distance)
|
|
616
|
+
best = { id, distance: dd };
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
|
|
620
|
+
if (!best)
|
|
621
|
+
return null;
|
|
622
|
+
for (const [q, L] of layers)
|
|
623
|
+
if (best.id >= L.base && best.id < L.base + L.n) {
|
|
624
|
+
const { lngLat } = hitOf(L, best.id - L.base), { layer, index, cell } = pickedCell(q, L, best.id - L.base);
|
|
625
|
+
return { id: best.id, q, layer, index, cell, value: null, distance: best.distance, lngLat, properties: { ...(L.properties || {}), index, cell, value: null } };
|
|
626
|
+
}
|
|
627
|
+
return null;
|
|
628
|
+
}
|
|
629
|
+
export function destroy() { layers.forEach(drop); layers.clear(); glc = null; }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { VoxelMessage, VoxelPickResult, CamState, VoxelApi, PickContext, OverlayHost } from "./voxel-gl.js";
|
|
2
|
+
export interface VoxelGpuModule {
|
|
3
|
+
init(canvas: OffscreenCanvas, opts: {
|
|
4
|
+
depth?: boolean;
|
|
5
|
+
}, host: OverlayHost | null): Promise<void>;
|
|
6
|
+
message(d: VoxelMessage): void;
|
|
7
|
+
frame(cam: unknown, s: CamState, size: {
|
|
8
|
+
w: number;
|
|
9
|
+
h: number;
|
|
10
|
+
}, api: VoxelApi): boolean;
|
|
11
|
+
pick(x: number, y: number, r: number, ctx: PickContext | null | undefined): Promise<VoxelPickResult | null>;
|
|
12
|
+
destroy(): void;
|
|
13
|
+
readonly shared: boolean;
|
|
14
|
+
}
|
|
15
|
+
export interface EnuBasis {
|
|
16
|
+
E: number[];
|
|
17
|
+
N: number[];
|
|
18
|
+
U: number[];
|
|
19
|
+
u: number[];
|
|
20
|
+
}
|
|
21
|
+
export declare function enuBasis(lon: number, lat: number, rAx?: number): EnuBasis;
|
|
22
|
+
export declare function create(): VoxelGpuModule;
|