@pirireis/webglobeplugins 0.17.0 → 0.17.1
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/Math/methods.js +6 -0
- package/Math/tessellation/tile-merger.js +284 -42
- package/Math/tessellation/triangle-tessellation-meta.js +22 -15
- package/investigation-tools/draw/tiles/adapters.js +67 -0
- package/investigation-tools/draw/tiles/tiles.js +128 -0
- package/package.json +1 -1
- package/programs/polygon-on-globe/texture-dem-triangle-test-plugin-triangle.js +155 -31
- package/programs/polygon-on-globe/texture-dem-triangles.js +68 -36
- package/programs/vectorfields/logics/drawrectangleparticles.js +8 -6
- package/programs/vectorfields/logics/drawrectangleparticles1.js +112 -0
- package/semiplugins/shape-on-terrain/arc-plugin.js +9 -0
- package/tracks/point-tracks/plugin.js +1 -0
- package/util/picking/picker-displayer.js +2 -2
- package/util/shaderfunctions/geometrytransformations.js +1 -1
- package/vectorfield/wind/plugin.js +33 -19
- package/write-text/context-text4.js +2 -1
- package/programs/polygon-on-globe/partial-tesselation.js +0 -1
- package/programs/polygon-on-globe/texture-dem-triangle-test-plugin.js +0 -118
- package/util/webglobe/rasteroverlay.js +0 -76
- package/write-text/attached-text-writer.js +0 -95
package/Math/methods.js
CHANGED
|
@@ -16,6 +16,12 @@ export const radianToMercator = (xy) => {
|
|
|
16
16
|
WORLD_RADIUS_MERCATOR * Math.log(Math.tan(Math.PI / 4 + xy[1] / 2))
|
|
17
17
|
];
|
|
18
18
|
};
|
|
19
|
+
export function radianToMercatorXY(xy) {
|
|
20
|
+
return [
|
|
21
|
+
xy[0] * Math.pow(2, 8),
|
|
22
|
+
Math.log(Math.tan(Math.PI / 4 + xy[1] / 2)) * Math.pow(2, 8)
|
|
23
|
+
];
|
|
24
|
+
}
|
|
19
25
|
export const radianToCartesian3d = (xy) => {
|
|
20
26
|
const x = Math.cos(xy[1]) * Math.cos(xy[0]);
|
|
21
27
|
const y = Math.cos(xy[1]) * Math.sin(xy[0]);
|
|
@@ -1,56 +1,298 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
1
|
+
function displayTileMetaData(tilesMetaData) {
|
|
2
|
+
// show maxZoom minZoom
|
|
3
|
+
console.log(`Max Level: ${tilesMetaData.maxLevel}
|
|
4
|
+
Min Level: ${tilesMetaData.minLevel}`);
|
|
5
|
+
}
|
|
6
|
+
function keyMethod(row, column, level) {
|
|
7
|
+
return `${level}_${row}_${column}`;
|
|
7
8
|
}
|
|
8
|
-
function
|
|
9
|
-
|
|
9
|
+
function calculateParentTile(row, column, level) {
|
|
10
|
+
return {
|
|
11
|
+
row: Math.floor(row / 2),
|
|
12
|
+
column: Math.floor(column / 2),
|
|
13
|
+
level: level - 1
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
function createMapAndMetaData(tiles) {
|
|
17
|
+
const tileMap = new Map();
|
|
18
|
+
let maxLevel = -Infinity;
|
|
19
|
+
let minLevel = Infinity;
|
|
20
|
+
const tileLimitsByZoom = new Map();
|
|
10
21
|
for (const tile of tiles) {
|
|
11
|
-
|
|
12
|
-
|
|
22
|
+
const key = keyMethod(tile.row, tile.column, tile.level);
|
|
23
|
+
tileMap.set(key, tile);
|
|
24
|
+
if (tile.level > maxLevel) {
|
|
25
|
+
maxLevel = tile.level;
|
|
26
|
+
}
|
|
27
|
+
if (tile.level < minLevel) {
|
|
28
|
+
minLevel = tile.level;
|
|
29
|
+
}
|
|
30
|
+
if (!tileLimitsByZoom.has(tile.level)) {
|
|
31
|
+
tileLimitsByZoom.set(tile.level, {
|
|
32
|
+
minRow: tile.row,
|
|
33
|
+
maxRow: tile.row,
|
|
34
|
+
minCol: tile.column,
|
|
35
|
+
maxCol: tile.column
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const limits = tileLimitsByZoom.get(tile.level);
|
|
40
|
+
if (tile.row < limits.minRow)
|
|
41
|
+
limits.minRow = tile.row;
|
|
42
|
+
if (tile.row > limits.maxRow)
|
|
43
|
+
limits.maxRow = tile.row;
|
|
44
|
+
if (tile.column < limits.minCol)
|
|
45
|
+
limits.minCol = tile.column;
|
|
46
|
+
if (tile.column > limits.maxCol)
|
|
47
|
+
limits.maxCol = tile.column;
|
|
13
48
|
}
|
|
14
|
-
map.get(tile.z).push(tile);
|
|
15
49
|
}
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
50
|
+
return {
|
|
51
|
+
tileMap,
|
|
52
|
+
maxLevel,
|
|
53
|
+
minLevel,
|
|
54
|
+
tileLimitsByZoom
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
function populateTileIntoHigherZoomLevel(tileMesh, tileDimensionLength, targetDimensionLength) {
|
|
58
|
+
const newMesh = new Array(targetDimensionLength).fill(0).map(() => new Float32Array(targetDimensionLength));
|
|
59
|
+
// use linear interpolation to populate
|
|
60
|
+
for (let row = 0; row < targetDimensionLength; row++) {
|
|
61
|
+
for (let col = 0; col < targetDimensionLength; col++) {
|
|
62
|
+
const srcRow = row * (tileDimensionLength - 1) / (targetDimensionLength - 1);
|
|
63
|
+
const srcCol = col * (tileDimensionLength - 1) / (targetDimensionLength - 1);
|
|
64
|
+
const srcRowLow = Math.floor(srcRow);
|
|
65
|
+
const srcRowHigh = Math.min(Math.ceil(srcRow), tileDimensionLength - 1);
|
|
66
|
+
const srcColLow = Math.floor(srcCol);
|
|
67
|
+
const srcColHigh = Math.min(Math.ceil(srcCol), tileDimensionLength - 1);
|
|
68
|
+
const topLeft = tileMesh[srcRowLow][srcColLow];
|
|
69
|
+
const topRight = tileMesh[srcRowLow][srcColHigh];
|
|
70
|
+
const bottomLeft = tileMesh[srcRowHigh][srcColLow];
|
|
71
|
+
const bottomRight = tileMesh[srcRowHigh][srcColHigh];
|
|
72
|
+
const top = topLeft + (topRight - topLeft) * (srcCol - srcColLow);
|
|
73
|
+
const bottom = bottomLeft + (bottomRight - bottomLeft) * (srcCol - srcColLow);
|
|
74
|
+
newMesh[row][col] = top + (bottom - top) * (srcRow - srcRowLow);
|
|
27
75
|
}
|
|
28
76
|
}
|
|
77
|
+
return newMesh;
|
|
29
78
|
}
|
|
30
|
-
function
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
let
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
79
|
+
// This function is correct, assuming its inputs are prepared properly.
|
|
80
|
+
// This function is correct, assuming its inputs are prepared properly.
|
|
81
|
+
function moveMeshToMergedMesh(targetMesh, targetDimensionLength, sourceMesh, sourceDimensionLength, startX, startY) {
|
|
82
|
+
for (let row = 0; row < sourceDimensionLength; row++) {
|
|
83
|
+
for (let col = 0; col < sourceDimensionLength; col++) {
|
|
84
|
+
const targetIndex = (startY + row) * targetDimensionLength + (startX + col);
|
|
85
|
+
// Check if source value exists
|
|
86
|
+
if (sourceMesh[row] === undefined || sourceMesh[row][col] === undefined) {
|
|
87
|
+
console.warn(`Source data missing at [${row}][${col}]`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
const value = sourceMesh[row][col];
|
|
91
|
+
if (isNaN(value)) {
|
|
92
|
+
console.warn(`NaN value found in source mesh at row: ${row}, col: ${col}`);
|
|
93
|
+
}
|
|
94
|
+
;
|
|
95
|
+
if (targetIndex >= targetMesh.length) {
|
|
96
|
+
console.warn(`⚠️ Target index out of bounds: ${targetIndex} >= ${targetMesh.length}`);
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
targetMesh[targetIndex] = value;
|
|
38
100
|
}
|
|
39
|
-
|
|
40
|
-
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
function limitCheck(limit, limitRange = 12) {
|
|
104
|
+
const rowRange = limit.maxRow - limit.minRow + 1;
|
|
105
|
+
const colRange = limit.maxCol - limit.minCol + 1;
|
|
106
|
+
if (rowRange > limitRange || colRange > limitRange) {
|
|
107
|
+
console.warn(`Tile limit range exceeded: Row Range = ${rowRange}, Col Range = ${colRange}, Limit Range = ${limitRange}`);
|
|
108
|
+
throw new Error("Tile limit range exceeded");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function mergeTiles(tilesMetaData, mergeCount = 8, tileDimensionLength = 5, processLimit = 6) {
|
|
112
|
+
const mergedMeshDimLength = tileDimensionLength * mergeCount - (mergeCount - 1);
|
|
113
|
+
const processedTiles = new Set();
|
|
114
|
+
const result = [];
|
|
115
|
+
for (let zoom = tilesMetaData.maxLevel; zoom >= tilesMetaData.minLevel; zoom--) {
|
|
116
|
+
let processedTileCountFromCurrentLevel = 0;
|
|
117
|
+
let processedTileCountFromParentLevel = 0;
|
|
118
|
+
if (tilesMetaData.maxLevel - zoom >= processLimit) {
|
|
119
|
+
break;
|
|
41
120
|
}
|
|
42
|
-
|
|
43
|
-
|
|
121
|
+
const limits = tilesMetaData.tileLimitsByZoom.get(zoom);
|
|
122
|
+
const mergedMesh = new Float32Array(mergedMeshDimLength * mergedMeshDimLength).fill(0);
|
|
123
|
+
const bboxLimit = {
|
|
124
|
+
ur: { x: -Infinity, y: -Infinity },
|
|
125
|
+
ll: { x: Infinity, y: Infinity }
|
|
126
|
+
};
|
|
127
|
+
if (!limits)
|
|
128
|
+
continue;
|
|
129
|
+
limitCheck(limits, 12);
|
|
130
|
+
for (let row = limits.minRow; row <= limits.maxRow; row++) {
|
|
131
|
+
for (let col = limits.minCol; col <= limits.maxCol; col++) {
|
|
132
|
+
const key = keyMethod(row, col, zoom);
|
|
133
|
+
if (processedTiles.has(key)) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
const tile = tilesMetaData.tileMap.get(key);
|
|
137
|
+
if (tile) {
|
|
138
|
+
// move tile mesh to merged mesh
|
|
139
|
+
moveMeshToMergedMesh(mergedMesh, mergedMeshDimLength, tile.mesh, tileDimensionLength, (row - limits.minRow) * (tileDimensionLength - 1), (col - limits.minCol) * (tileDimensionLength - 1));
|
|
140
|
+
// moveMeshToMergedMesh2(
|
|
141
|
+
// mergedMesh, mergedMeshDimLength,
|
|
142
|
+
// (row + col) % 2 === 0 ? 0.1 : 0,
|
|
143
|
+
// tileDimensionLength,
|
|
144
|
+
// (row - limits.minRow) * (tileDimensionLength - 1), (col - limits.minCol) * (tileDimensionLength - 1)
|
|
145
|
+
// );
|
|
146
|
+
processedTiles.add(key);
|
|
147
|
+
processedTileCountFromCurrentLevel++;
|
|
148
|
+
// update bbox
|
|
149
|
+
if (tile.bbox.ll.x < bboxLimit.ll.x)
|
|
150
|
+
bboxLimit.ll.x = tile.bbox.ll.x;
|
|
151
|
+
if (tile.bbox.ll.y < bboxLimit.ll.y)
|
|
152
|
+
bboxLimit.ll.y = tile.bbox.ll.y;
|
|
153
|
+
if (tile.bbox.ur.x > bboxLimit.ur.x)
|
|
154
|
+
bboxLimit.ur.x = tile.bbox.ur.x;
|
|
155
|
+
if (tile.bbox.ur.y > bboxLimit.ur.y)
|
|
156
|
+
bboxLimit.ur.y = tile.bbox.ur.y;
|
|
157
|
+
/**
|
|
158
|
+
} else {
|
|
159
|
+
// try to find parent tile
|
|
160
|
+
const parent = calculateParentTile(row, col, zoom);
|
|
161
|
+
const parentKey = keyMethod(parent.row, parent.column, parent.level);
|
|
162
|
+
const parentTile = tilesMetaData.tileMap.get(parentKey);
|
|
163
|
+
|
|
164
|
+
if (parentTile) {
|
|
165
|
+
// Parent's 4 children at current zoom level
|
|
166
|
+
const parentTopLeftChildRow = parent.row * 2;
|
|
167
|
+
const parentTopLeftChildCol = parent.column * 2;
|
|
168
|
+
const topLeftChildKey = keyMethod(parentTopLeftChildRow, parentTopLeftChildCol, zoom);
|
|
169
|
+
|
|
170
|
+
// Only process parent once
|
|
171
|
+
if (!processedTiles.has(topLeftChildKey)) {
|
|
172
|
+
// Check if parent's area is within or overlaps the current limits
|
|
173
|
+
const parentBottomRightChildRow = parentTopLeftChildRow + 1;
|
|
174
|
+
const parentBottomRightChildCol = parentTopLeftChildCol + 1;
|
|
175
|
+
|
|
176
|
+
// Check if parent's children overlap with current zoom's limits
|
|
177
|
+
if (parentTopLeftChildRow > limits.maxRow ||
|
|
178
|
+
parentBottomRightChildRow < limits.minRow ||
|
|
179
|
+
parentTopLeftChildCol > limits.maxCol ||
|
|
180
|
+
parentBottomRightChildCol < limits.minCol) {
|
|
181
|
+
// Parent doesn't overlap, skip
|
|
182
|
+
processedTiles.add(key);
|
|
183
|
+
continue;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// Upscale parent to cover all 4 children
|
|
187
|
+
const parentTileUpscaledMesh = populateTileIntoHigherZoomLevel(
|
|
188
|
+
parentTile.mesh,
|
|
189
|
+
tileDimensionLength,
|
|
190
|
+
tileDimensionLength * 2 - 1
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Calculate destination position (parent's top-left child position)
|
|
194
|
+
// Clamp to limits to handle edge cases
|
|
195
|
+
const actualStartRow = Math.max(parentTopLeftChildRow, limits.minRow);
|
|
196
|
+
const actualStartCol = Math.max(parentTopLeftChildCol, limits.minCol);
|
|
197
|
+
|
|
198
|
+
const destRow = (actualStartRow - limits.minRow) * (tileDimensionLength - 1);
|
|
199
|
+
const destCol = (actualStartCol - limits.minCol) * (tileDimensionLength - 1);
|
|
200
|
+
|
|
201
|
+
// Calculate source offset if parent starts before limits
|
|
202
|
+
const srcRowOffset = (actualStartRow - parentTopLeftChildRow) * (tileDimensionLength - 1);
|
|
203
|
+
const srcColOffset = (actualStartCol - parentTopLeftChildCol) * (tileDimensionLength - 1);
|
|
204
|
+
|
|
205
|
+
// Calculate how much to copy
|
|
206
|
+
const actualEndRow = Math.min(parentBottomRightChildRow, limits.maxRow);
|
|
207
|
+
const actualEndCol = Math.min(parentBottomRightChildCol, limits.maxCol);
|
|
208
|
+
|
|
209
|
+
const copyRows = (actualEndRow - actualStartRow + 1) * (tileDimensionLength - 1);
|
|
210
|
+
const copyCols = (actualEndCol - actualStartCol + 1) * (tileDimensionLength - 1);
|
|
211
|
+
|
|
212
|
+
// Copy only the visible portion
|
|
213
|
+
moveMeshToMergedMeshPartial(
|
|
214
|
+
mergedMesh,
|
|
215
|
+
mergedMeshDimLength,
|
|
216
|
+
parentTileUpscaledMesh,
|
|
217
|
+
tileDimensionLength * 2 - 1,
|
|
218
|
+
destRow,
|
|
219
|
+
destCol,
|
|
220
|
+
srcRowOffset,
|
|
221
|
+
srcColOffset,
|
|
222
|
+
copyRows,
|
|
223
|
+
copyCols
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
// Mark all 4 child positions as processed (even if out of bounds)
|
|
227
|
+
for (let dr = 0; dr < 2; dr++) {
|
|
228
|
+
for (let dc = 0; dc < 2; dc++) {
|
|
229
|
+
const childRow = parentTopLeftChildRow + dr;
|
|
230
|
+
const childCol = parentTopLeftChildCol + dc;
|
|
231
|
+
const childKey = keyMethod(childRow, childCol, zoom);
|
|
232
|
+
processedTiles.add(childKey);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
processedTileCountFromParentLevel++;
|
|
237
|
+
|
|
238
|
+
// Update bbox (use actual parent bbox, not child bbox)
|
|
239
|
+
if (parentTile.bbox.ll.x < bboxLimit.ll.x) bboxLimit.ll.x = parentTile.bbox.ll.x;
|
|
240
|
+
if (parentTile.bbox.ll.y < bboxLimit.ll.y) bboxLimit.ll.y = parentTile.bbox.ll.y;
|
|
241
|
+
if (parentTile.bbox.ur.x > bboxLimit.ur.x) bboxLimit.ur.x = parentTile.bbox.ur.x;
|
|
242
|
+
if (parentTile.bbox.ur.y > bboxLimit.ur.y) bboxLimit.ur.y = parentTile.bbox.ur.y;
|
|
243
|
+
} else {
|
|
244
|
+
// Already processed via parent
|
|
245
|
+
processedTiles.add(key);
|
|
246
|
+
}
|
|
247
|
+
} else {
|
|
248
|
+
// No parent available
|
|
249
|
+
processedTiles.add(key);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
*/
|
|
253
|
+
}
|
|
254
|
+
}
|
|
44
255
|
}
|
|
45
|
-
|
|
46
|
-
|
|
256
|
+
const rowRatio = (limits.maxRow - limits.minRow + 1) / mergeCount;
|
|
257
|
+
const colRatio = (limits.maxCol - limits.minCol + 1) / mergeCount;
|
|
258
|
+
// console.log(`Zoom ${zoom}: Processed ${processedTileCountFromCurrentLevel} from current level, ${processedTileCountFromParentLevel} from parent level. Coverage: ${(rowRatio * 100).toFixed(1)}% x ${(colRatio * 100).toFixed(1)}%`);
|
|
259
|
+
result.push({ mesh: mergedMesh, bbox: bboxLimit, zoom: zoom, coverRatio: { x: rowRatio, y: colRatio } });
|
|
260
|
+
}
|
|
261
|
+
return result;
|
|
262
|
+
}
|
|
263
|
+
export function mergeMeshes(tiles, mergeCount = 12, tileDimensionLength = 5, processLimit = 6) {
|
|
264
|
+
// filter out tiles with NaN values
|
|
265
|
+
// return returnTop6tiles(tiles);
|
|
266
|
+
const tilesMetaData = createMapAndMetaData(tiles);
|
|
267
|
+
const mergedTiles = mergeTiles(tilesMetaData, mergeCount, tileDimensionLength, processLimit);
|
|
268
|
+
return mergedTiles;
|
|
269
|
+
}
|
|
270
|
+
function returnTop6tiles(tiles) {
|
|
271
|
+
const tilesMetaData = createMapAndMetaData(tiles);
|
|
272
|
+
const hightestZoom = tilesMetaData.maxLevel;
|
|
273
|
+
const result = [];
|
|
274
|
+
let counter = 6;
|
|
275
|
+
function tileToMergedTileInfo(tile) {
|
|
276
|
+
const mesh = new Float32Array(tile.mesh[0].length * tile.mesh[0].length);
|
|
277
|
+
for (let row = 0; row < tile.mesh[0].length; row++) {
|
|
278
|
+
for (let col = 0; col < tile.mesh[0].length; col++) {
|
|
279
|
+
mesh[row * tile.mesh[0].length + col] = tile.mesh[0][row * tile.mesh[0].length + col];
|
|
280
|
+
}
|
|
47
281
|
}
|
|
282
|
+
return {
|
|
283
|
+
mesh: mesh,
|
|
284
|
+
zoom: tile.level,
|
|
285
|
+
bbox: tile.bbox,
|
|
286
|
+
coverRatio: { x: 1, y: 1 },
|
|
287
|
+
};
|
|
48
288
|
}
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
289
|
+
for (const tile of tiles) {
|
|
290
|
+
if (counter === 0)
|
|
291
|
+
break;
|
|
292
|
+
if (tile.level === hightestZoom) {
|
|
293
|
+
result.push(tileToMergedTileInfo(tile));
|
|
294
|
+
counter--;
|
|
295
|
+
}
|
|
54
296
|
}
|
|
55
|
-
return
|
|
297
|
+
return result;
|
|
56
298
|
}
|
|
@@ -8,6 +8,7 @@ import { pointsOnArc } from "../juction/arc-plane";
|
|
|
8
8
|
import { latToTileY, tileYtoLat } from "./methods";
|
|
9
9
|
import { isOnTileEdge } from "./methods";
|
|
10
10
|
import Delaunator from "delaunator";
|
|
11
|
+
import { radianToMercatorXY } from "../methods";
|
|
11
12
|
// TODO:get rid of embedded lists. always flat list
|
|
12
13
|
const TILE_DEM_VERTEX_COUNT = 5; // 5x5 grid for DEM
|
|
13
14
|
const TILE_DEM_STEPCOUNT = TILE_DEM_VERTEX_COUNT - 1; // 4 inner divisions in each dimension
|
|
@@ -271,6 +272,11 @@ export function getAllPoints(triangleMeta, zoom, innerCuts = TILE_DEM_STEPCOUNT)
|
|
|
271
272
|
const edgeIndexes = shellPoints.map((e, index) => index * 2); // edge points are always first points in list
|
|
272
273
|
indices = filteroutEdgeConnections(indices, edgeIndexes);
|
|
273
274
|
// rotateIndices(indices);
|
|
275
|
+
// for (let i = 0; i < longLatPoints.length / 2; i++) {
|
|
276
|
+
// const xy = radianToMercatorXY([longLatPoints[i * 2], longLatPoints[i * 2 + 1]]);
|
|
277
|
+
// longLatPoints[i * 2] = xy[0];
|
|
278
|
+
// longLatPoints[i * 2 + 1] = xy[1];
|
|
279
|
+
// }
|
|
274
280
|
return { vec3s: new Float32Array(points), longLats: new Float32Array(longLatPoints), indices: indices };
|
|
275
281
|
}
|
|
276
282
|
function fillBetweenInParallels(lat, p1, p2, step, fillVec3Arr, longLatPoints) {
|
|
@@ -307,16 +313,6 @@ function filterDuplicate(points) {
|
|
|
307
313
|
function showLongLatRadian(longLat) {
|
|
308
314
|
return `(${(longLat[0] * 180 / Math.PI).toFixed(2)}°, ${(longLat[1] * 180 / Math.PI).toFixed(2)}°)`;
|
|
309
315
|
}
|
|
310
|
-
function rotateIndices(indices) {
|
|
311
|
-
const len = indices.length / 3;
|
|
312
|
-
let hold;
|
|
313
|
-
for (let i = 0; i < len; i++) {
|
|
314
|
-
hold = indices[i * 3];
|
|
315
|
-
indices[i * 3] = indices[i * 3 + 1];
|
|
316
|
-
indices[i * 3 + 1] = hold;
|
|
317
|
-
}
|
|
318
|
-
return indices;
|
|
319
|
-
}
|
|
320
316
|
// This method for removing external edge connections. The connections are unnecessary and cause a veil descending to the center of sphere from the edges
|
|
321
317
|
function filteroutEdgeConnections(indexes, filterOutIndexes) {
|
|
322
318
|
const length = indexes.length / 3;
|
|
@@ -377,6 +373,7 @@ export function partialTessellation(triangleMeta, limits, innerCuts) {
|
|
|
377
373
|
const allVec3s = [];
|
|
378
374
|
const allLongLats = [];
|
|
379
375
|
let pointCounter = 0;
|
|
376
|
+
const stepCount = innerCuts - 1; //
|
|
380
377
|
/**
|
|
381
378
|
* Precision for coordinate keys in the map. 1e-12 radians is
|
|
382
379
|
* extremely small (sub-millimeter on Earth's surface) and avoids
|
|
@@ -432,14 +429,16 @@ export function partialTessellation(triangleMeta, limits, innerCuts) {
|
|
|
432
429
|
}
|
|
433
430
|
// 2b. Calculate grid steps for this tile's zoom
|
|
434
431
|
const tileCount = TILE_COUNTS[zoom];
|
|
435
|
-
const lonStep = (2 * Math.PI) / tileCount /
|
|
436
|
-
const latStep = 1.0 /
|
|
432
|
+
const lonStep = (2 * Math.PI) / tileCount / stepCount;
|
|
433
|
+
const latStep = 1.0 / stepCount; // This is in tileY units
|
|
437
434
|
// 2c. Generate Latitude (Parallel) Points
|
|
438
435
|
const startTileY = latToTileY(workBBox.max[1], zoom); // max lat -> min tileY
|
|
439
436
|
const endTileY = latToTileY(workBBox.min[1], zoom); // min lat -> max tileY
|
|
440
|
-
let currentY = Math.ceil(startTileY / latStep) * latStep;
|
|
441
|
-
|
|
442
|
-
|
|
437
|
+
// let currentY = Math.ceil(startTileY / latStep) * latStep;
|
|
438
|
+
let currentY = startTileY - Math.abs(startTileY % latStep);
|
|
439
|
+
// if (currentY < startTileY - 1e-9) currentY += latStep; // Ensure we start inside or on edge
|
|
440
|
+
if (currentY === startTileY)
|
|
441
|
+
currentY -= latStep; // since start point is already added
|
|
443
442
|
while (currentY <= endTileY + 1e-9) {
|
|
444
443
|
const lat = tileYtoLat(currentY, zoom);
|
|
445
444
|
// Skip if rounding put us slightly outside the work box
|
|
@@ -508,6 +507,14 @@ export function partialTessellation(triangleMeta, limits, innerCuts) {
|
|
|
508
507
|
let indices = delaunator.triangles;
|
|
509
508
|
// 4. Filter out triangles connected to the "shell" points
|
|
510
509
|
indices = filteroutEdgeConnections(indices, shellPointIndices);
|
|
510
|
+
// console.log(...allLongLats.slice(0, 10));
|
|
511
|
+
for (let i = 0; i < allLongLats.length / 2; i++) {
|
|
512
|
+
const xy = radianToMercatorXY([allLongLats[i * 2], allLongLats[i * 2 + 1]]);
|
|
513
|
+
allLongLats[i * 2] = xy[0];
|
|
514
|
+
allLongLats[i * 2 + 1] = xy[1];
|
|
515
|
+
}
|
|
516
|
+
// show first 5
|
|
517
|
+
// console.log(...allLongLats.slice(0, 10));
|
|
511
518
|
return {
|
|
512
519
|
vec3s: new Float32Array(allVec3s),
|
|
513
520
|
longLats: new Float32Array(allLongLats),
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
function boilerArcInput(start, end, color, key) {
|
|
2
|
+
return {
|
|
3
|
+
key: key,
|
|
4
|
+
start: start,
|
|
5
|
+
end: end,
|
|
6
|
+
color: color,
|
|
7
|
+
height: null,
|
|
8
|
+
msl: null
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
export function tileToArcInputs(tiles, color) {
|
|
12
|
+
if (tiles.length === 0)
|
|
13
|
+
return [];
|
|
14
|
+
const result = [];
|
|
15
|
+
// (alias) type ArcInput = {
|
|
16
|
+
// key: string;
|
|
17
|
+
// start: LongLat;
|
|
18
|
+
// end: LongLat;
|
|
19
|
+
// color: Color;
|
|
20
|
+
// height?: Meter | null;
|
|
21
|
+
// msl?: boolean | null;
|
|
22
|
+
// }
|
|
23
|
+
for (const tile of tiles) {
|
|
24
|
+
const tileKey = `tile_${tile.row}_${tile.column}`;
|
|
25
|
+
const bbox = tile.bbox;
|
|
26
|
+
// left side
|
|
27
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ll.y], [bbox.ll.x, bbox.ur.y], color, `${tileKey}_left`));
|
|
28
|
+
// top side
|
|
29
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ur.y], [bbox.ur.x, bbox.ur.y], color, `${tileKey}_top`));
|
|
30
|
+
// bottom side
|
|
31
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ll.y], [bbox.ur.x, bbox.ll.y], color, `${tileKey}_bottom`));
|
|
32
|
+
// right side
|
|
33
|
+
result.push(boilerArcInput([bbox.ur.x, bbox.ll.y], [bbox.ur.x, bbox.ur.y], color, `${tileKey}_right`));
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
export function mergedTileToArcInputs(mergedTile, color) {
|
|
38
|
+
const result = [];
|
|
39
|
+
const bbox = mergedTile.bbox;
|
|
40
|
+
// left side
|
|
41
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ll.y], [bbox.ll.x, bbox.ur.y], color, `${mergedTile.zoom}_left`));
|
|
42
|
+
// right side
|
|
43
|
+
result.push(boilerArcInput([bbox.ur.x, bbox.ll.y], [bbox.ur.x, bbox.ur.y], color, `${mergedTile.zoom}_right`));
|
|
44
|
+
// top side
|
|
45
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ur.y], [bbox.ur.x, bbox.ur.y], color, `${mergedTile.zoom}_top`));
|
|
46
|
+
// bottom side
|
|
47
|
+
result.push(boilerArcInput([bbox.ll.x, bbox.ll.y], [bbox.ur.x, bbox.ll.y], color, `${mergedTile.zoom}_bottom`));
|
|
48
|
+
return result;
|
|
49
|
+
}
|
|
50
|
+
export function tilesToContextWriter4Inputs(tiles) {
|
|
51
|
+
const result = [];
|
|
52
|
+
for (const tile of tiles) {
|
|
53
|
+
const tileKey = `L_${tile.level}_R${tile.row}_C${tile.column}`;
|
|
54
|
+
const centerLong = (tile.bbox.ll.x + tile.bbox.ur.x) / 2;
|
|
55
|
+
const centerLat = (tile.bbox.ll.y + tile.bbox.ur.y) / 2;
|
|
56
|
+
result.push({
|
|
57
|
+
key: tileKey,
|
|
58
|
+
long: centerLong,
|
|
59
|
+
lat: centerLat,
|
|
60
|
+
text: tileKey,
|
|
61
|
+
opacity: null,
|
|
62
|
+
angle: null,
|
|
63
|
+
position: 'center'
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
return result;
|
|
67
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { ArcOnTerrainPlugin } from "../../../semiplugins/shape-on-terrain/arc-plugin";
|
|
2
|
+
import { tileToArcInputs, tilesToContextWriter4Inputs, mergedTileToArcInputs } from "./adapters";
|
|
3
|
+
import { ContextTextWriter4 } from "../../../write-text/context-text4";
|
|
4
|
+
import { mergeMeshes } from "../../../Math/tessellation/tile-merger";
|
|
5
|
+
// diverse ColorRamp
|
|
6
|
+
const defaultColorsRamp = [
|
|
7
|
+
[0.0, [0, 0, 255, 255]], // Blue
|
|
8
|
+
// [0.3, [255, 165, 0, 255]], // Orange
|
|
9
|
+
[0.25, [0, 255, 0, 255]], // Green
|
|
10
|
+
// [0.4, [255, 0, 0, 255]], // Red
|
|
11
|
+
[0.5, [128, 0, 128, 255]], // Purple
|
|
12
|
+
// [0.6, [0, 255, 255, 255]], // Cyan
|
|
13
|
+
[0.75, [255, 192, 203, 255]], // Pink
|
|
14
|
+
[1.0, [255, 255, 255, 255]], // White
|
|
15
|
+
// [1.0, [0, 0, 0, 255]] // Black
|
|
16
|
+
];
|
|
17
|
+
export class TilesRendererPlugin {
|
|
18
|
+
id;
|
|
19
|
+
globe;
|
|
20
|
+
gl;
|
|
21
|
+
arcPlugin;
|
|
22
|
+
contextTextWriter;
|
|
23
|
+
colorsRamp;
|
|
24
|
+
_lastTilesUniqueIdentifier = null;
|
|
25
|
+
constructor(id, colorsRamp = defaultColorsRamp) {
|
|
26
|
+
this.globe = null;
|
|
27
|
+
this.gl = null;
|
|
28
|
+
this.contextTextWriter = null;
|
|
29
|
+
this.id = id;
|
|
30
|
+
this.colorsRamp = colorsRamp;
|
|
31
|
+
this.arcPlugin = new ArcOnTerrainPlugin(id + "_arc_plugin", {
|
|
32
|
+
variativeColorsOn: true,
|
|
33
|
+
defaultHeightFromGroundIn3D: 0,
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
init(globe, gl) {
|
|
37
|
+
this.globe = globe;
|
|
38
|
+
this.gl = gl;
|
|
39
|
+
this.contextTextWriter = new ContextTextWriter4(globe, (item) => { return [item]; }, (item) => { return item.key; }, (zoomLevel) => (item) => {
|
|
40
|
+
return {
|
|
41
|
+
opacityMultiplier: 1,
|
|
42
|
+
sizeMultiplier: 1
|
|
43
|
+
};
|
|
44
|
+
});
|
|
45
|
+
this.arcPlugin.init(globe, gl);
|
|
46
|
+
}
|
|
47
|
+
draw3D(projectionMatrix, modelViewMatrix, transPosition) {
|
|
48
|
+
// if (this.globe.api_IsScreenMoving()) {
|
|
49
|
+
this._prepareArcInputs();
|
|
50
|
+
// }
|
|
51
|
+
this.arcPlugin.draw3D();
|
|
52
|
+
this.contextTextWriter.draw();
|
|
53
|
+
}
|
|
54
|
+
free() {
|
|
55
|
+
this.arcPlugin.free();
|
|
56
|
+
}
|
|
57
|
+
_prepareArcInputs() {
|
|
58
|
+
// const drawnTiles = filterTilesInScreen(
|
|
59
|
+
// this.globe.api_GetDrawedTilesInfo(),
|
|
60
|
+
// this.globe
|
|
61
|
+
// );
|
|
62
|
+
const drawnTiles = this.globe.api_GetDrawedTilesInfo();
|
|
63
|
+
let maxLevel = 0;
|
|
64
|
+
let minLevel = Number.MAX_VALUE;
|
|
65
|
+
const tileMap = new Map();
|
|
66
|
+
for (const tile of drawnTiles) {
|
|
67
|
+
if (tile.level > maxLevel) {
|
|
68
|
+
maxLevel = tile.level;
|
|
69
|
+
}
|
|
70
|
+
if (tile.level < minLevel) {
|
|
71
|
+
minLevel = tile.level;
|
|
72
|
+
}
|
|
73
|
+
if (!tileMap.has(tile.level)) {
|
|
74
|
+
tileMap.set(tile.level, []);
|
|
75
|
+
}
|
|
76
|
+
tileMap.get(tile.level).push(tile);
|
|
77
|
+
}
|
|
78
|
+
let tilesUniqueId = "";
|
|
79
|
+
for (let level = minLevel; level <= maxLevel; level++) {
|
|
80
|
+
const tiles = tileMap.get(level);
|
|
81
|
+
if (!tiles)
|
|
82
|
+
continue;
|
|
83
|
+
tilesUniqueId += `L${level}_C${tiles.length}_`;
|
|
84
|
+
}
|
|
85
|
+
if (tilesUniqueId === this._lastTilesUniqueIdentifier)
|
|
86
|
+
return;
|
|
87
|
+
this._lastTilesUniqueIdentifier = tilesUniqueId;
|
|
88
|
+
const levelRange = maxLevel - minLevel;
|
|
89
|
+
const colors = this._interpolateColors(levelRange);
|
|
90
|
+
this.arcPlugin.deleteAll();
|
|
91
|
+
for (let level = maxLevel; level >= minLevel; level--) {
|
|
92
|
+
const tiles = tileMap.get(level);
|
|
93
|
+
if (!tiles)
|
|
94
|
+
continue;
|
|
95
|
+
const colorIndex = level - minLevel;
|
|
96
|
+
const color = colors[colorIndex];
|
|
97
|
+
const arcInputs = tileToArcInputs(tiles, color);
|
|
98
|
+
this.arcPlugin.insertBulk(arcInputs);
|
|
99
|
+
}
|
|
100
|
+
// drawMergedBBoxes:
|
|
101
|
+
const mergedTiles = mergeMeshes(drawnTiles, 12, 5, 4);
|
|
102
|
+
const color = [255, 255, 0, 255]; // Yellow
|
|
103
|
+
const arcMergedInputs = mergedTiles.map(mergedTile => mergedTileToArcInputs(mergedTile, color)).flat();
|
|
104
|
+
this.arcPlugin.insertBulk(arcMergedInputs);
|
|
105
|
+
const contextWriterInputs = tilesToContextWriter4Inputs(drawnTiles);
|
|
106
|
+
this.contextTextWriter.clear();
|
|
107
|
+
this.contextTextWriter.insertTextBulk(contextWriterInputs);
|
|
108
|
+
}
|
|
109
|
+
_interpolateColors(maxValue) {
|
|
110
|
+
let colors = [];
|
|
111
|
+
let colorRampIndex = 0;
|
|
112
|
+
for (let i = 0; i <= maxValue; i++) {
|
|
113
|
+
const t = i / maxValue;
|
|
114
|
+
while (colorRampIndex < this.colorsRamp.length - 1 && t > this.colorsRamp[colorRampIndex + 1][0]) {
|
|
115
|
+
colorRampIndex++;
|
|
116
|
+
}
|
|
117
|
+
const [t0, color0] = this.colorsRamp[colorRampIndex];
|
|
118
|
+
const [t1, color1] = this.colorsRamp[Math.min(colorRampIndex + 1, this.colorsRamp.length - 1)];
|
|
119
|
+
const localT = (t - t0) / (t1 - t0);
|
|
120
|
+
const r = Math.round(color0[0] + (color1[0] - color0[0]) * localT);
|
|
121
|
+
const g = Math.round(color0[1] + (color1[1] - color0[1]) * localT);
|
|
122
|
+
const b = Math.round(color0[2] + (color1[2] - color0[2]) * localT);
|
|
123
|
+
// const a = Math.round(color0[3] + (color1[3] - color0[3]) * localT);
|
|
124
|
+
colors.push([r, g, b, 255]);
|
|
125
|
+
}
|
|
126
|
+
return colors;
|
|
127
|
+
}
|
|
128
|
+
}
|