signalk-livedepth-plugin 0.3.4 → 0.3.12
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/CHANGELOG.md +70 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/public/js/config.js +160 -9
- package/public/js/config.js.map +2 -2
- package/public/js/render-worker.js +259 -31
- package/public/js/render-worker.js.map +3 -3
- package/public/js/renderer.js +14 -4
- package/public/js/renderer.js.map +2 -2
- package/public/styles.css +17 -0
- package/dist/tiles/accumulator.d.ts +0 -34
- package/dist/tiles/accumulator.d.ts.map +0 -1
- package/dist/tiles/accumulator.js +0 -60
- package/dist/tiles/accumulator.js.map +0 -1
- package/dist/tiles/grid.d.ts +0 -59
- package/dist/tiles/grid.d.ts.map +0 -1
- package/dist/tiles/grid.js +0 -121
- package/dist/tiles/grid.js.map +0 -1
|
@@ -163,6 +163,7 @@ uniform float uShadeLimit;
|
|
|
163
163
|
uniform int uLevels;
|
|
164
164
|
uniform float uInterval;
|
|
165
165
|
uniform float uLineWidth;
|
|
166
|
+
uniform float uIsobars; // 1 = per-fragment isobars, 0 = vector contours drawn by the runtime
|
|
166
167
|
bool uncov(float v) { return v < 0.0; } // -1: no coverage
|
|
167
168
|
float sampleDepth(vec2 uv) {
|
|
168
169
|
vec2 fc = uv / uTexel - 0.5;
|
|
@@ -193,16 +194,87 @@ void main() {
|
|
|
193
194
|
float tCenter = (band + 0.5) / float(uLevels);
|
|
194
195
|
fill = texture(uLut, vec2(clamp(tCenter, 0.0, 1.0), 0.5)).rgb;
|
|
195
196
|
}
|
|
196
|
-
// Isobars at every absolute multiple of the interval, over ALL data.
|
|
197
|
-
|
|
198
|
-
float
|
|
199
|
-
|
|
200
|
-
|
|
197
|
+
// Isobars at every absolute multiple of the interval, over ALL data. The
|
|
198
|
+
// runtime can disable these and draw vector contours instead.
|
|
199
|
+
float line = 0.0;
|
|
200
|
+
if (uIsobars > 0.5) {
|
|
201
|
+
float ci = d / uInterval;
|
|
202
|
+
float dv = min(fract(ci), 1.0 - fract(ci));
|
|
203
|
+
float px = dv / max(fwidth(ci), 1e-6);
|
|
204
|
+
line = 1.0 - smoothstep(uLineWidth, uLineWidth + 1.5, px);
|
|
205
|
+
}
|
|
201
206
|
vec3 col = mix(fill, vec3(0.0), line);
|
|
202
207
|
float alpha = max(line, shaded ? 1.0 : 0.0);
|
|
203
208
|
fragColor = vec4(col, alpha);
|
|
204
209
|
}`;
|
|
205
210
|
|
|
211
|
+
// src/web/shared/contours.ts
|
|
212
|
+
function onGrid(level, step) {
|
|
213
|
+
const n = level / step;
|
|
214
|
+
return Math.abs(n - Math.round(n)) < 1e-6;
|
|
215
|
+
}
|
|
216
|
+
function extractContours(field, minorStep, majorStep, maxLevel) {
|
|
217
|
+
const { width, height, depth, extent } = field;
|
|
218
|
+
const result = { major: [], minor: [] };
|
|
219
|
+
const minor = minorStep > 0 ? minorStep : 1;
|
|
220
|
+
const major = majorStep > 0 ? majorStep : minor;
|
|
221
|
+
if (width < 2 || height < 2) return result;
|
|
222
|
+
const cell = (extent[2] - extent[0]) / width;
|
|
223
|
+
const at = (gx, gy) => depth[gy * width + gx];
|
|
224
|
+
const ox = [0, 1, 1, 0];
|
|
225
|
+
const oy = [0, 0, 1, 1];
|
|
226
|
+
const step = Math.min(minor, major);
|
|
227
|
+
for (let gy = 0; gy < height - 1; gy++) {
|
|
228
|
+
for (let gx = 0; gx < width - 1; gx++) {
|
|
229
|
+
const d = [
|
|
230
|
+
at(gx, gy),
|
|
231
|
+
at(gx + 1, gy),
|
|
232
|
+
at(gx + 1, gy + 1),
|
|
233
|
+
at(gx, gy + 1)
|
|
234
|
+
];
|
|
235
|
+
if (!Number.isFinite(d[0]) || !Number.isFinite(d[1]) || !Number.isFinite(d[2]) || !Number.isFinite(d[3])) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
const lo = Math.min(d[0], d[1], d[2], d[3]);
|
|
239
|
+
const hi = Math.max(d[0], d[1], d[2], d[3]);
|
|
240
|
+
if (hi <= lo) continue;
|
|
241
|
+
const first = Math.floor(lo / step) * step + step;
|
|
242
|
+
for (let level = first; level < hi && level <= maxLevel; level += step) {
|
|
243
|
+
if (level <= lo) continue;
|
|
244
|
+
const out = onGrid(level, major) ? result.major : result.minor;
|
|
245
|
+
const cross = [null, null, null, null];
|
|
246
|
+
const crossed = [];
|
|
247
|
+
for (let i = 0; i < 4; i++) {
|
|
248
|
+
const j = (i + 1) % 4;
|
|
249
|
+
if (d[i] >= level === d[j] >= level) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
const t = (level - d[i]) / (d[j] - d[i]);
|
|
253
|
+
cross[i] = [
|
|
254
|
+
extent[0] + (gx + ox[i] + (ox[j] - ox[i]) * t) * cell,
|
|
255
|
+
extent[1] + (gy + oy[i] + (oy[j] - oy[i]) * t) * cell
|
|
256
|
+
];
|
|
257
|
+
crossed.push(i);
|
|
258
|
+
}
|
|
259
|
+
if (crossed.length === 2) {
|
|
260
|
+
const [a, b] = crossed;
|
|
261
|
+
out.push([...cross[a], ...cross[b]]);
|
|
262
|
+
} else if (crossed.length === 4) {
|
|
263
|
+
const centre = (d[0] + d[1] + d[2] + d[3]) / 4;
|
|
264
|
+
if (centre >= level) {
|
|
265
|
+
out.push([...cross[0], ...cross[1]]);
|
|
266
|
+
out.push([...cross[2], ...cross[3]]);
|
|
267
|
+
} else {
|
|
268
|
+
out.push([...cross[0], ...cross[3]]);
|
|
269
|
+
out.push([...cross[1], ...cross[2]]);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
return result;
|
|
276
|
+
}
|
|
277
|
+
|
|
206
278
|
// src/web/shared/live-depth.ts
|
|
207
279
|
var DEFAULT_SHADE_LIMIT_M = 10;
|
|
208
280
|
var EARTH_RADIUS2 = 6378137;
|
|
@@ -222,11 +294,11 @@ function latOf(y) {
|
|
|
222
294
|
return (2 * Math.atan(Math.exp(y / EARTH_RADIUS2)) - Math.PI / 2) / RAD2;
|
|
223
295
|
}
|
|
224
296
|
var SHADE_RAMP = [
|
|
225
|
-
[0,
|
|
226
|
-
[0.
|
|
227
|
-
[0.
|
|
228
|
-
[0.
|
|
229
|
-
[1,
|
|
297
|
+
[0, 37, 175, 248],
|
|
298
|
+
[0.25, 143, 215, 248],
|
|
299
|
+
[0.5, 167, 223, 248],
|
|
300
|
+
[0.75, 183, 223, 248],
|
|
301
|
+
[1, 248, 248, 248]
|
|
230
302
|
];
|
|
231
303
|
var SHADE_CONTOUR_LEVELS = 6;
|
|
232
304
|
function shadeColor(t) {
|
|
@@ -417,11 +489,21 @@ var liveSamples = [];
|
|
|
417
489
|
var settings = null;
|
|
418
490
|
var builder = null;
|
|
419
491
|
var fieldDirty = true;
|
|
492
|
+
var fieldVersion = 0;
|
|
493
|
+
var surfaceCache = null;
|
|
494
|
+
var outCanvas = null;
|
|
495
|
+
var outCtx = null;
|
|
496
|
+
var SURFACE_PAD = 0.25;
|
|
497
|
+
var overlayCache = null;
|
|
498
|
+
var overlayCanvas = null;
|
|
499
|
+
var overlayCtx = null;
|
|
420
500
|
var vessel = { position: null };
|
|
421
501
|
var depthUnit = "m";
|
|
422
502
|
var samplesVersion = 0;
|
|
423
503
|
var shoalCache = null;
|
|
424
504
|
var clusterCache = null;
|
|
505
|
+
var contours = { major: [], minor: [] };
|
|
506
|
+
var gpuMsaa = true;
|
|
425
507
|
var AUTO_FRAME_BUDGET_MS = 10;
|
|
426
508
|
var AUTO_ADJUST_EVERY = 30;
|
|
427
509
|
var PERF_REPORT_EVERY = 20;
|
|
@@ -430,6 +512,7 @@ var frameAvgMs = 0;
|
|
|
430
512
|
var framesSinceAdjust = 0;
|
|
431
513
|
var perfFrames = 0;
|
|
432
514
|
function renderScale() {
|
|
515
|
+
if (settings?.fullResolution) return 1;
|
|
433
516
|
const quality = settings?.quality ?? "auto";
|
|
434
517
|
return quality === "auto" ? autoScale : QUALITY_SCALE[quality];
|
|
435
518
|
}
|
|
@@ -452,6 +535,7 @@ var uShadeLimit = null;
|
|
|
452
535
|
var uLevels = null;
|
|
453
536
|
var uInterval = null;
|
|
454
537
|
var uLineWidth = null;
|
|
538
|
+
var uIsobars = null;
|
|
455
539
|
function compile(ctx, type, src) {
|
|
456
540
|
const shader = ctx.createShader(type);
|
|
457
541
|
if (!shader) return null;
|
|
@@ -466,11 +550,14 @@ function compile(ctx, type, src) {
|
|
|
466
550
|
function initWebgl() {
|
|
467
551
|
if (gl) return true;
|
|
468
552
|
glCanvas = new OffscreenCanvas(1, 1);
|
|
553
|
+
const msaa = settings?.msaa ?? true;
|
|
469
554
|
const ctx = glCanvas.getContext("webgl2", {
|
|
470
555
|
alpha: true,
|
|
471
|
-
premultipliedAlpha: true
|
|
556
|
+
premultipliedAlpha: true,
|
|
557
|
+
antialias: msaa
|
|
472
558
|
});
|
|
473
559
|
if (!ctx) return false;
|
|
560
|
+
gpuMsaa = msaa;
|
|
474
561
|
const vs = compile(ctx, ctx.VERTEX_SHADER, DEPTH_VERT_SRC);
|
|
475
562
|
const fs = compile(ctx, ctx.FRAGMENT_SHADER, DEPTH_FRAG_SRC);
|
|
476
563
|
const prog = ctx.createProgram();
|
|
@@ -491,6 +578,7 @@ function initWebgl() {
|
|
|
491
578
|
uLevels = ctx.getUniformLocation(prog, "uLevels");
|
|
492
579
|
uInterval = ctx.getUniformLocation(prog, "uInterval");
|
|
493
580
|
uLineWidth = ctx.getUniformLocation(prog, "uLineWidth");
|
|
581
|
+
uIsobars = ctx.getUniformLocation(prog, "uIsobars");
|
|
494
582
|
quad = ctx.createBuffer();
|
|
495
583
|
ctx.bindBuffer(ctx.ARRAY_BUFFER, quad);
|
|
496
584
|
ctx.bufferData(
|
|
@@ -541,6 +629,14 @@ function releaseGpu() {
|
|
|
541
629
|
depthTexture = null;
|
|
542
630
|
lutTexture = null;
|
|
543
631
|
glCanvas = null;
|
|
632
|
+
surfaceCache?.image.close();
|
|
633
|
+
surfaceCache = null;
|
|
634
|
+
outCanvas = null;
|
|
635
|
+
outCtx = null;
|
|
636
|
+
overlayCache?.image.close();
|
|
637
|
+
overlayCache = null;
|
|
638
|
+
overlayCanvas = null;
|
|
639
|
+
overlayCtx = null;
|
|
544
640
|
}
|
|
545
641
|
function uploadField(f) {
|
|
546
642
|
if (!gl || !depthTexture) return;
|
|
@@ -562,6 +658,7 @@ function uploadField(f) {
|
|
|
562
658
|
gl.FLOAT,
|
|
563
659
|
gpu
|
|
564
660
|
);
|
|
661
|
+
fieldVersion++;
|
|
565
662
|
}
|
|
566
663
|
function uploadRegion(f, [x0, y0, x1, y1]) {
|
|
567
664
|
if (!gl || !depthTexture) return;
|
|
@@ -580,6 +677,7 @@ function uploadRegion(f, [x0, y0, x1, y1]) {
|
|
|
580
677
|
gl.activeTexture(gl.TEXTURE0);
|
|
581
678
|
gl.bindTexture(gl.TEXTURE_2D, depthTexture);
|
|
582
679
|
gl.texSubImage2D(gl.TEXTURE_2D, 0, x0, y0, w, h, gl.RED, gl.FLOAT, gpu);
|
|
680
|
+
fieldVersion++;
|
|
583
681
|
}
|
|
584
682
|
function rebuildField() {
|
|
585
683
|
builder = null;
|
|
@@ -610,9 +708,14 @@ function rebuildField() {
|
|
|
610
708
|
const tiles = settings.source === "tiles";
|
|
611
709
|
const gap = tiles ? 0 : medianConsecutiveGap(source);
|
|
612
710
|
const minCover = tiles ? 0 : DEFAULT_COVERAGE_MIN_M;
|
|
613
|
-
const
|
|
711
|
+
const maxCells = Math.max(
|
|
712
|
+
64,
|
|
713
|
+
Math.round(settings.fieldCells) || MAX_FIELD_CELLS
|
|
714
|
+
);
|
|
715
|
+
const cell = Math.max(gap, extMax / maxCells, minCover / 8, 0.01);
|
|
614
716
|
const next = createDepthFieldBuilder(extent, {
|
|
615
717
|
cellMetres: cell,
|
|
718
|
+
maxCells,
|
|
616
719
|
// `gap` bridges the along-track gaps, the fill radius widens a sounding
|
|
617
720
|
// into empty water, and a sounding always covers its own cell.
|
|
618
721
|
coverageMetres: Math.max(cell * 1.5, gap * 1.5, settings.fillMetres),
|
|
@@ -623,6 +726,12 @@ function rebuildField() {
|
|
|
623
726
|
if (liveSamples.length) next.add(liveSamples);
|
|
624
727
|
builder = next;
|
|
625
728
|
uploadField(next.field);
|
|
729
|
+
contours = settings.vectorContours ? extractContours(
|
|
730
|
+
next.field,
|
|
731
|
+
Math.max(0.01, settings.minorContourMetres),
|
|
732
|
+
Math.max(0.01, settings.contourIntervalMetres),
|
|
733
|
+
Math.max(0, settings.maxDepthMetres)
|
|
734
|
+
) : { major: [], minor: [] };
|
|
626
735
|
}
|
|
627
736
|
function addLive(newSamples) {
|
|
628
737
|
liveSamples.push(...newSamples);
|
|
@@ -669,11 +778,45 @@ function renderGpu(box, canvasW, canvasH, ratio) {
|
|
|
669
778
|
gl.uniform1i(uLevels, SHADE_CONTOUR_LEVELS);
|
|
670
779
|
gl.uniform1f(uInterval, Math.max(0.01, settings?.contourIntervalMetres ?? 1));
|
|
671
780
|
gl.uniform1f(uLineWidth, Math.max(1, ratio));
|
|
781
|
+
gl.uniform1f(uIsobars, settings?.vectorContours ? 0 : 1);
|
|
672
782
|
gl.bindBuffer(gl.ARRAY_BUFFER, quad);
|
|
673
783
|
gl.enableVertexAttribArray(aPos);
|
|
674
784
|
gl.vertexAttribPointer(aPos, 2, gl.FLOAT, false, 0, 0);
|
|
675
785
|
gl.drawArrays(gl.TRIANGLES, 0, 6);
|
|
676
786
|
}
|
|
787
|
+
function drawContours(ctx, box, canvasW, canvasH, ratio) {
|
|
788
|
+
if (!settings?.vectorContours) return;
|
|
789
|
+
const f = builder?.field;
|
|
790
|
+
if (!f || contours.major.length === 0 && contours.minor.length === 0) {
|
|
791
|
+
return;
|
|
792
|
+
}
|
|
793
|
+
const boxW = box[2] - box[0];
|
|
794
|
+
const boxH = box[3] - box[1];
|
|
795
|
+
if (!(boxW > 0) || !(boxH > 0)) return;
|
|
796
|
+
const ext = f.extent;
|
|
797
|
+
const shift = Math.round(((ext[0] + ext[2]) / 2 - (box[0] + box[2]) / 2) / WORLD_WIDTH) * WORLD_WIDTH;
|
|
798
|
+
const px = (x) => (x + shift - box[0]) / boxW * canvasW;
|
|
799
|
+
const py = (y) => (box[3] - y) / boxH * canvasH;
|
|
800
|
+
const stroke = (segments, color, widthPx) => {
|
|
801
|
+
if (segments.length === 0) return;
|
|
802
|
+
ctx.beginPath();
|
|
803
|
+
for (const [x0, y0, x1, y1] of segments) {
|
|
804
|
+
ctx.moveTo(px(x0), py(y0));
|
|
805
|
+
ctx.lineTo(px(x1), py(y1));
|
|
806
|
+
}
|
|
807
|
+
ctx.lineWidth = Math.max(0.5, widthPx * ratio);
|
|
808
|
+
ctx.lineCap = "round";
|
|
809
|
+
ctx.lineJoin = "round";
|
|
810
|
+
ctx.strokeStyle = color;
|
|
811
|
+
ctx.stroke();
|
|
812
|
+
};
|
|
813
|
+
stroke(
|
|
814
|
+
contours.minor,
|
|
815
|
+
settings.minorContourColor,
|
|
816
|
+
settings.minorContourWidthPx
|
|
817
|
+
);
|
|
818
|
+
stroke(contours.major, settings.majorContourColor, settings.contourWidthPx);
|
|
819
|
+
}
|
|
677
820
|
function drawOverlay(ctx, box, canvasW, canvasH, resolution, ratio, zoom) {
|
|
678
821
|
if (!settings) return;
|
|
679
822
|
const boxW = box[2] - box[0];
|
|
@@ -707,6 +850,7 @@ function drawOverlay(ctx, box, canvasW, canvasH, resolution, ratio, zoom) {
|
|
|
707
850
|
ctx.fillStyle = "rgba(0,0,0,0.95)";
|
|
708
851
|
ctx.fillText(text, p[0], p[1]);
|
|
709
852
|
};
|
|
853
|
+
drawContours(ctx, box, canvasW, canvasH, ratio);
|
|
710
854
|
const shoalKey = `${samplesVersion}:${settings.safetyDepthMetres}`;
|
|
711
855
|
if (!shoalCache || shoalCache.key !== shoalKey) {
|
|
712
856
|
shoalCache = {
|
|
@@ -731,7 +875,6 @@ function drawOverlay(ctx, box, canvasW, canvasH, resolution, ratio, zoom) {
|
|
|
731
875
|
const clusters = clusterCache.value;
|
|
732
876
|
for (const c of clusters) {
|
|
733
877
|
const p = toPixel(c.lon, c.lat);
|
|
734
|
-
dot(p, c.depth);
|
|
735
878
|
const radiusPx = c.radiusMetres / resolution * ratio;
|
|
736
879
|
if (radiusPx >= LABEL_MIN_RADIUS_PX) {
|
|
737
880
|
const fontCss = Math.max(
|
|
@@ -739,6 +882,8 @@ function drawOverlay(ctx, box, canvasW, canvasH, resolution, ratio, zoom) {
|
|
|
739
882
|
Math.min(LABEL_FONT_MAX, (zoom - 8) * 1.5 + LABEL_FONT_MIN)
|
|
740
883
|
);
|
|
741
884
|
label(formatDepth(c.depth), p, fontCss);
|
|
885
|
+
} else {
|
|
886
|
+
dot(p, c.depth);
|
|
742
887
|
}
|
|
743
888
|
}
|
|
744
889
|
const v = vessel;
|
|
@@ -793,31 +938,112 @@ function render(req) {
|
|
|
793
938
|
const surfaceW = Math.max(1, Math.round(width * scale));
|
|
794
939
|
const surfaceH = Math.max(1, Math.round(height * scale));
|
|
795
940
|
const started = performance.now();
|
|
796
|
-
|
|
797
|
-
|
|
941
|
+
if (!outCanvas || !outCtx || outCanvas.width !== width || outCanvas.height !== height) {
|
|
942
|
+
outCanvas = new OffscreenCanvas(width, height);
|
|
943
|
+
outCtx = outCanvas.getContext("2d");
|
|
944
|
+
}
|
|
945
|
+
const out = outCanvas;
|
|
946
|
+
const ctx = outCtx;
|
|
798
947
|
if (!ctx) {
|
|
799
948
|
post({ type: "error", message: "no 2D context" });
|
|
800
949
|
return;
|
|
801
950
|
}
|
|
802
951
|
ctx.clearRect(0, 0, width, height);
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
0,
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
952
|
+
let redrew = false;
|
|
953
|
+
if (builder && glCanvas) {
|
|
954
|
+
const padX = (box[2] - box[0]) * SURFACE_PAD;
|
|
955
|
+
const padY = (box[3] - box[1]) * SURFACE_PAD;
|
|
956
|
+
const padBox = [
|
|
957
|
+
box[0] - padX,
|
|
958
|
+
box[1] - padY,
|
|
959
|
+
box[2] + padX,
|
|
960
|
+
box[3] + padY
|
|
961
|
+
];
|
|
962
|
+
const padW = Math.max(1, Math.round(surfaceW * (1 + SURFACE_PAD * 2)));
|
|
963
|
+
const padH = Math.max(1, Math.round(surfaceH * (1 + SURFACE_PAD * 2)));
|
|
964
|
+
const surfaceKey = [
|
|
965
|
+
fieldVersion,
|
|
966
|
+
padW,
|
|
967
|
+
padH,
|
|
968
|
+
pixelRatio,
|
|
969
|
+
settings?.shadeLimitMetres,
|
|
970
|
+
settings?.contourIntervalMetres,
|
|
971
|
+
settings?.vectorContours,
|
|
972
|
+
settings?.msaa
|
|
973
|
+
].join("|");
|
|
974
|
+
const cached = surfaceCache;
|
|
975
|
+
const covers = !!cached && cached.key === surfaceKey && box[0] >= cached.box[0] && box[1] >= cached.box[1] && box[2] <= cached.box[2] && box[3] <= cached.box[3];
|
|
976
|
+
if (!covers) {
|
|
977
|
+
renderGpu(padBox, padW, padH, pixelRatio * scale);
|
|
978
|
+
cached?.image.close();
|
|
979
|
+
surfaceCache = {
|
|
980
|
+
key: surfaceKey,
|
|
981
|
+
image: glCanvas.transferToImageBitmap(),
|
|
982
|
+
box: padBox,
|
|
983
|
+
width: padW,
|
|
984
|
+
height: padH
|
|
985
|
+
};
|
|
986
|
+
redrew = true;
|
|
987
|
+
}
|
|
988
|
+
const cache = surfaceCache;
|
|
989
|
+
const cw = cache.box[2] - cache.box[0];
|
|
990
|
+
const ch = cache.box[3] - cache.box[1];
|
|
991
|
+
const sx = (box[0] - cache.box[0]) / cw * cache.width;
|
|
992
|
+
const sy = (cache.box[3] - box[3]) / ch * cache.height;
|
|
993
|
+
const sw = (box[2] - box[0]) / cw * cache.width;
|
|
994
|
+
const sh = (box[3] - box[1]) / ch * cache.height;
|
|
995
|
+
ctx.drawImage(cache.image, sx, sy, sw, sh, 0, 0, width, height);
|
|
816
996
|
}
|
|
817
|
-
|
|
997
|
+
const overlayKey = [
|
|
998
|
+
fieldVersion,
|
|
999
|
+
samplesVersion,
|
|
1000
|
+
box.join(","),
|
|
1001
|
+
width,
|
|
1002
|
+
height,
|
|
1003
|
+
resolution,
|
|
1004
|
+
pixelRatio,
|
|
1005
|
+
zoom,
|
|
1006
|
+
settings?.vectorContours,
|
|
1007
|
+
settings?.contourIntervalMetres,
|
|
1008
|
+
settings?.minorContourMetres,
|
|
1009
|
+
settings?.contourWidthPx,
|
|
1010
|
+
settings?.minorContourWidthPx,
|
|
1011
|
+
settings?.majorContourColor,
|
|
1012
|
+
settings?.minorContourColor,
|
|
1013
|
+
settings?.safetyDepthMetres,
|
|
1014
|
+
settings?.cellPx,
|
|
1015
|
+
settings?.mergeTolerance,
|
|
1016
|
+
settings?.maxDepthMetres,
|
|
1017
|
+
settings?.shadeLimitMetres
|
|
1018
|
+
].join("|");
|
|
1019
|
+
if (!overlayCache || overlayCache.key !== overlayKey) {
|
|
1020
|
+
if (!overlayCanvas || !overlayCtx || overlayCanvas.width !== width || overlayCanvas.height !== height) {
|
|
1021
|
+
overlayCanvas = new OffscreenCanvas(width, height);
|
|
1022
|
+
overlayCtx = overlayCanvas.getContext("2d");
|
|
1023
|
+
}
|
|
1024
|
+
if (overlayCtx) {
|
|
1025
|
+
overlayCtx.clearRect(0, 0, width, height);
|
|
1026
|
+
drawOverlay(
|
|
1027
|
+
overlayCtx,
|
|
1028
|
+
box,
|
|
1029
|
+
width,
|
|
1030
|
+
height,
|
|
1031
|
+
resolution,
|
|
1032
|
+
pixelRatio,
|
|
1033
|
+
zoom
|
|
1034
|
+
);
|
|
1035
|
+
overlayCache?.image.close();
|
|
1036
|
+
overlayCache = {
|
|
1037
|
+
key: overlayKey,
|
|
1038
|
+
image: overlayCanvas.transferToImageBitmap()
|
|
1039
|
+
};
|
|
1040
|
+
redrew = true;
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
if (overlayCache) ctx.drawImage(overlayCache.image, 0, 0);
|
|
818
1044
|
const image = out.transferToImageBitmap();
|
|
819
1045
|
const frameMs = performance.now() - started;
|
|
820
|
-
if ((settings?.quality ?? "auto") === "auto") {
|
|
1046
|
+
if (redrew && (settings?.quality ?? "auto") === "auto") {
|
|
821
1047
|
frameAvgMs = frameAvgMs === 0 ? frameMs : frameAvgMs * 0.8 + frameMs * 0.2;
|
|
822
1048
|
if (++framesSinceAdjust >= AUTO_ADJUST_EVERY) {
|
|
823
1049
|
framesSinceAdjust = 0;
|
|
@@ -854,10 +1080,12 @@ self.onmessage = (e) => {
|
|
|
854
1080
|
case "vessel":
|
|
855
1081
|
vessel = msg;
|
|
856
1082
|
break;
|
|
857
|
-
case "settings":
|
|
1083
|
+
case "settings": {
|
|
1084
|
+
if (msg.settings.msaa !== gpuMsaa) releaseGpu();
|
|
858
1085
|
settings = msg.settings;
|
|
859
1086
|
fieldDirty = true;
|
|
860
1087
|
break;
|
|
1088
|
+
}
|
|
861
1089
|
case "units":
|
|
862
1090
|
depthUnit = msg.depth;
|
|
863
1091
|
break;
|