@tonybfox/threejs-tools 1.0.5 → 1.0.7
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/dist/asset-loader/index.cjs +3 -417
- package/dist/asset-loader/index.mjs +1 -6
- package/dist/camera/index.cjs +1 -393
- package/dist/camera/index.mjs +1 -6
- package/dist/chunk-4O4ENFL7.mjs +83 -0
- package/dist/chunk-55YVGK52.mjs +1 -0
- package/dist/chunk-B75TYEOO.mjs +44 -0
- package/dist/chunk-CLSRN5D2.mjs +1 -0
- package/dist/chunk-EQRUOKFV.mjs +1 -0
- package/dist/chunk-JRJBW66X.mjs +1 -0
- package/dist/chunk-OJFYE56U.mjs +1 -0
- package/dist/chunk-WZ4X7GQ2.mjs +1 -0
- package/dist/chunk-Z5VL3O6M.mjs +43 -0
- package/dist/compass/index.cjs +3 -304
- package/dist/compass/index.mjs +1 -6
- package/dist/grid/index.cjs +3 -159
- package/dist/grid/index.mjs +1 -6
- package/dist/index.cjs +7 -5406
- package/dist/index.mjs +1 -384
- package/dist/measurements/index.cjs +1 -1197
- package/dist/measurements/index.mjs +1 -8
- package/dist/sunlight/index.cjs +1 -440
- package/dist/sunlight/index.d.mts +19 -0
- package/dist/sunlight/index.d.ts +19 -0
- package/dist/sunlight/index.mjs +1 -6
- package/dist/terrain/index.cjs +1 -422
- package/dist/terrain/index.mjs +1 -6
- package/dist/transform-controls/index.cjs +1 -1586
- package/dist/transform-controls/index.mjs +1 -12
- package/dist/view-helper/index.cjs +1 -435
- package/dist/view-helper/index.mjs +1 -6
- package/package.json +1 -1
- package/dist/chunk-2CDI7ORN.mjs +0 -163
- package/dist/chunk-FBTT6MU6.mjs +0 -386
- package/dist/chunk-IAZH4OHC.mjs +0 -399
- package/dist/chunk-LUE7VHLK.mjs +0 -422
- package/dist/chunk-OZKJ3GAD.mjs +0 -1160
- package/dist/chunk-W4DAAAW6.mjs +0 -404
- package/dist/chunk-XA7OKYSM.mjs +0 -357
- package/dist/chunk-YMMLYGHV.mjs +0 -1578
- package/dist/chunk-ZNGFST7K.mjs +0 -348
package/dist/terrain/index.cjs
CHANGED
|
@@ -1,422 +1 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __create = Object.create;
|
|
3
|
-
var __defProp = Object.defineProperty;
|
|
4
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
-
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
-
var __export = (target, all) => {
|
|
9
|
-
for (var name in all)
|
|
10
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
-
};
|
|
12
|
-
var __copyProps = (to, from, except, desc) => {
|
|
13
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
-
for (let key of __getOwnPropNames(from))
|
|
15
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
-
}
|
|
18
|
-
return to;
|
|
19
|
-
};
|
|
20
|
-
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
-
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
-
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
-
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
-
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
-
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
-
mod
|
|
27
|
-
));
|
|
28
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
-
|
|
30
|
-
// packages/terrain/src/index.ts
|
|
31
|
-
var src_exports = {};
|
|
32
|
-
__export(src_exports, {
|
|
33
|
-
TerrainTool: () => TerrainTool
|
|
34
|
-
});
|
|
35
|
-
module.exports = __toCommonJS(src_exports);
|
|
36
|
-
|
|
37
|
-
// packages/terrain/src/TerrainTool.ts
|
|
38
|
-
var THREE = __toESM(require("three"));
|
|
39
|
-
var TerrainTool = class extends THREE.EventDispatcher {
|
|
40
|
-
/**
|
|
41
|
-
* Creates a new TerrainTool instance
|
|
42
|
-
* @param scene - The Three.js scene to add terrain to
|
|
43
|
-
* @param options - Configuration options
|
|
44
|
-
*/
|
|
45
|
-
constructor(scene, options = {}) {
|
|
46
|
-
super();
|
|
47
|
-
this.mesh = null;
|
|
48
|
-
this.currentData = null;
|
|
49
|
-
this.isLoading = false;
|
|
50
|
-
this.generatedTextureUrls = /* @__PURE__ */ new Set();
|
|
51
|
-
this.scene = scene;
|
|
52
|
-
this.widthSegments = options.widthSegments ?? 50;
|
|
53
|
-
this.depthSegments = options.depthSegments ?? 50;
|
|
54
|
-
this.elevationScale = options.elevationScale ?? 1;
|
|
55
|
-
this.baseColor = options.baseColor ?? 9139029;
|
|
56
|
-
this.wireframe = options.wireframe ?? false;
|
|
57
|
-
this.textureUrl = options.textureUrl;
|
|
58
|
-
this.mapboxOptions = options.mapbox;
|
|
59
|
-
this.receiveShadow = options.receiveShadow ?? true;
|
|
60
|
-
this.castShadow = options.castShadow ?? true;
|
|
61
|
-
this.useDemoData = options.useDemoData ?? false;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Update Mapbox imagery configuration at runtime
|
|
65
|
-
*/
|
|
66
|
-
setMapboxOptions(options) {
|
|
67
|
-
this.mapboxOptions = options;
|
|
68
|
-
}
|
|
69
|
-
/**
|
|
70
|
-
* Loads terrain data and creates a mesh
|
|
71
|
-
* @param center - Center coordinates (latitude, longitude)
|
|
72
|
-
* @param dimensions - Terrain dimensions in meters
|
|
73
|
-
*/
|
|
74
|
-
async loadTerrain(center, dimensions) {
|
|
75
|
-
if (this.isLoading) {
|
|
76
|
-
console.warn("Terrain is already loading");
|
|
77
|
-
return;
|
|
78
|
-
}
|
|
79
|
-
this.isLoading = true;
|
|
80
|
-
this.dispatchEvent({
|
|
81
|
-
type: "updateStarted",
|
|
82
|
-
center,
|
|
83
|
-
dimensions
|
|
84
|
-
});
|
|
85
|
-
try {
|
|
86
|
-
const terrainData = await this.fetchElevationData(center, dimensions);
|
|
87
|
-
this.currentData = terrainData;
|
|
88
|
-
this.dispatchEvent({
|
|
89
|
-
type: "dataLoaded",
|
|
90
|
-
data: terrainData
|
|
91
|
-
});
|
|
92
|
-
const textureSource = await this.resolveTexture(center, dimensions);
|
|
93
|
-
await this.createMesh(terrainData, textureSource);
|
|
94
|
-
} catch (error) {
|
|
95
|
-
console.error("Error loading terrain:", error);
|
|
96
|
-
this.dispatchEvent({
|
|
97
|
-
type: "error",
|
|
98
|
-
message: "Failed to load terrain",
|
|
99
|
-
error
|
|
100
|
-
});
|
|
101
|
-
} finally {
|
|
102
|
-
this.isLoading = false;
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* Updates the terrain with new coordinates and/or dimensions
|
|
107
|
-
* @param center - New center coordinates (optional, keeps current if not provided)
|
|
108
|
-
* @param dimensions - New dimensions (optional, keeps current if not provided)
|
|
109
|
-
*/
|
|
110
|
-
async updateTerrain(center, dimensions) {
|
|
111
|
-
if (!this.currentData && !center) {
|
|
112
|
-
console.warn("No current terrain data and no center provided");
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
const newCenter = center || this.currentData.center;
|
|
116
|
-
const newDimensions = dimensions || this.currentData.dimensions;
|
|
117
|
-
await this.loadTerrain(newCenter, newDimensions);
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Fetches elevation data from Open-Elevation API or generates demo data
|
|
121
|
-
* @private
|
|
122
|
-
*/
|
|
123
|
-
async fetchElevationData(center, dimensions) {
|
|
124
|
-
if (this.useDemoData) {
|
|
125
|
-
return this.generateDemoData(center, dimensions);
|
|
126
|
-
}
|
|
127
|
-
const points = [];
|
|
128
|
-
const metersPerDegreeLat = 111320;
|
|
129
|
-
const metersPerDegreeLon = 111320 * Math.max(Math.abs(Math.cos(center.latitude * Math.PI / 180)), 1e-6);
|
|
130
|
-
const latRange = dimensions.depth / metersPerDegreeLat;
|
|
131
|
-
const lonRange = dimensions.width / metersPerDegreeLon;
|
|
132
|
-
const latStep = latRange / this.depthSegments;
|
|
133
|
-
const lonStep = lonRange / this.widthSegments;
|
|
134
|
-
const startLat = center.latitude + latRange / 2;
|
|
135
|
-
const startLon = center.longitude - lonRange / 2;
|
|
136
|
-
for (let z = 0; z <= this.depthSegments; z++) {
|
|
137
|
-
for (let x = 0; x <= this.widthSegments; x++) {
|
|
138
|
-
points.push({
|
|
139
|
-
latitude: startLat - z * latStep,
|
|
140
|
-
longitude: startLon + x * lonStep
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
const response = await fetch(
|
|
145
|
-
"https://api.open-elevation.com/api/v1/lookup",
|
|
146
|
-
{
|
|
147
|
-
method: "POST",
|
|
148
|
-
headers: {
|
|
149
|
-
"Content-Type": "application/json"
|
|
150
|
-
},
|
|
151
|
-
body: JSON.stringify({
|
|
152
|
-
locations: points
|
|
153
|
-
})
|
|
154
|
-
}
|
|
155
|
-
);
|
|
156
|
-
if (!response.ok) {
|
|
157
|
-
throw new Error(`Elevation API error: ${response.statusText}`);
|
|
158
|
-
}
|
|
159
|
-
const data = await response.json();
|
|
160
|
-
const results = data.results;
|
|
161
|
-
const elevations = [];
|
|
162
|
-
let minElevation = Infinity;
|
|
163
|
-
let maxElevation = -Infinity;
|
|
164
|
-
for (let z = 0; z <= this.depthSegments; z++) {
|
|
165
|
-
const row = [];
|
|
166
|
-
for (let x = 0; x <= this.widthSegments; x++) {
|
|
167
|
-
const index = z * (this.widthSegments + 1) + x;
|
|
168
|
-
const elevation = results[index].elevation;
|
|
169
|
-
row.push(elevation);
|
|
170
|
-
minElevation = Math.min(minElevation, elevation);
|
|
171
|
-
maxElevation = Math.max(maxElevation, elevation);
|
|
172
|
-
}
|
|
173
|
-
elevations.push(row);
|
|
174
|
-
}
|
|
175
|
-
return {
|
|
176
|
-
center,
|
|
177
|
-
dimensions,
|
|
178
|
-
elevations,
|
|
179
|
-
minElevation,
|
|
180
|
-
maxElevation
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
/**
|
|
184
|
-
* Generates demo terrain data using perlin-like noise
|
|
185
|
-
* @private
|
|
186
|
-
*/
|
|
187
|
-
generateDemoData(center, dimensions) {
|
|
188
|
-
const elevations = [];
|
|
189
|
-
let minElevation = Infinity;
|
|
190
|
-
let maxElevation = -Infinity;
|
|
191
|
-
const seed = center.latitude + center.longitude;
|
|
192
|
-
for (let z = 0; z <= this.depthSegments; z++) {
|
|
193
|
-
const row = [];
|
|
194
|
-
for (let x = 0; x <= this.widthSegments; x++) {
|
|
195
|
-
const nx = x / this.widthSegments * 4;
|
|
196
|
-
const nz = z / this.depthSegments * 4;
|
|
197
|
-
let elevation = 0;
|
|
198
|
-
elevation += Math.sin(nx + seed) * 500;
|
|
199
|
-
elevation += Math.cos(nz + seed) * 500;
|
|
200
|
-
elevation += Math.sin(nx * 2 + seed) * 200;
|
|
201
|
-
elevation += Math.cos(nz * 2 + seed) * 200;
|
|
202
|
-
elevation += Math.sin(nx * 4 + seed) * 50;
|
|
203
|
-
elevation += Math.cos(nz * 4 + seed) * 50;
|
|
204
|
-
elevation += (Math.random() - 0.5) * 30;
|
|
205
|
-
row.push(elevation);
|
|
206
|
-
minElevation = Math.min(minElevation, elevation);
|
|
207
|
-
maxElevation = Math.max(maxElevation, elevation);
|
|
208
|
-
}
|
|
209
|
-
elevations.push(row);
|
|
210
|
-
}
|
|
211
|
-
return {
|
|
212
|
-
center,
|
|
213
|
-
dimensions,
|
|
214
|
-
elevations,
|
|
215
|
-
minElevation,
|
|
216
|
-
maxElevation
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Creates a terrain mesh from elevation data
|
|
221
|
-
* @private
|
|
222
|
-
*/
|
|
223
|
-
async createMesh(data, textureSource) {
|
|
224
|
-
if (this.mesh) {
|
|
225
|
-
this.scene.remove(this.mesh);
|
|
226
|
-
this.mesh.geometry.dispose();
|
|
227
|
-
if (Array.isArray(this.mesh.material)) {
|
|
228
|
-
this.mesh.material.forEach((mat) => mat.dispose());
|
|
229
|
-
} else {
|
|
230
|
-
this.mesh.material.dispose();
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
const geometry = new THREE.PlaneGeometry(
|
|
234
|
-
data.dimensions.width,
|
|
235
|
-
data.dimensions.depth,
|
|
236
|
-
this.widthSegments,
|
|
237
|
-
this.depthSegments
|
|
238
|
-
);
|
|
239
|
-
const positions = geometry.attributes.position;
|
|
240
|
-
let vertexIndex = 0;
|
|
241
|
-
for (let z = 0; z <= this.depthSegments; z++) {
|
|
242
|
-
for (let x = 0; x <= this.widthSegments; x++) {
|
|
243
|
-
const elevation = data.elevations[z][x];
|
|
244
|
-
const height = (elevation - data.minElevation) * this.elevationScale;
|
|
245
|
-
positions.setZ(vertexIndex, height);
|
|
246
|
-
vertexIndex++;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
geometry.computeVertexNormals();
|
|
250
|
-
let material;
|
|
251
|
-
if (textureSource?.url) {
|
|
252
|
-
try {
|
|
253
|
-
const texture = await new THREE.TextureLoader().loadAsync(
|
|
254
|
-
textureSource.url
|
|
255
|
-
);
|
|
256
|
-
material = new THREE.MeshStandardMaterial({
|
|
257
|
-
map: texture,
|
|
258
|
-
wireframe: this.wireframe
|
|
259
|
-
});
|
|
260
|
-
} catch (error) {
|
|
261
|
-
console.warn("Failed to load terrain texture, using base color.", error);
|
|
262
|
-
material = new THREE.MeshStandardMaterial({
|
|
263
|
-
color: this.baseColor,
|
|
264
|
-
wireframe: this.wireframe
|
|
265
|
-
});
|
|
266
|
-
} finally {
|
|
267
|
-
if (textureSource.revokeOnUse) {
|
|
268
|
-
this.releaseGeneratedTexture(textureSource.url);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
} else {
|
|
272
|
-
material = new THREE.MeshStandardMaterial({
|
|
273
|
-
color: this.baseColor,
|
|
274
|
-
wireframe: this.wireframe
|
|
275
|
-
});
|
|
276
|
-
}
|
|
277
|
-
this.mesh = new THREE.Mesh(geometry, material);
|
|
278
|
-
this.mesh.rotation.x = -Math.PI / 2;
|
|
279
|
-
this.mesh.receiveShadow = this.receiveShadow;
|
|
280
|
-
this.mesh.castShadow = this.castShadow;
|
|
281
|
-
this.scene.add(this.mesh);
|
|
282
|
-
this.dispatchEvent({
|
|
283
|
-
type: "meshLoaded",
|
|
284
|
-
mesh: this.mesh
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
async resolveTexture(center, dimensions) {
|
|
288
|
-
if (this.textureUrl) {
|
|
289
|
-
return {
|
|
290
|
-
url: this.textureUrl,
|
|
291
|
-
revokeOnUse: false
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
if (!this.mapboxOptions) {
|
|
295
|
-
return void 0;
|
|
296
|
-
}
|
|
297
|
-
try {
|
|
298
|
-
const url = await this.fetchMapboxTexture(
|
|
299
|
-
center,
|
|
300
|
-
dimensions,
|
|
301
|
-
this.mapboxOptions
|
|
302
|
-
);
|
|
303
|
-
this.generatedTextureUrls.add(url);
|
|
304
|
-
return {
|
|
305
|
-
url,
|
|
306
|
-
revokeOnUse: true
|
|
307
|
-
};
|
|
308
|
-
} catch (error) {
|
|
309
|
-
console.warn(
|
|
310
|
-
"Failed to fetch Mapbox imagery, falling back to base material.",
|
|
311
|
-
error
|
|
312
|
-
);
|
|
313
|
-
return void 0;
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
computeBoundingBox(center, dimensions, paddingRatio) {
|
|
317
|
-
const metersPerDegreeLat = 111320;
|
|
318
|
-
const latRadians = center.latitude * Math.PI / 180;
|
|
319
|
-
const cosLat = Math.cos(latRadians);
|
|
320
|
-
const metersPerDegreeLon = 111320 * Math.max(Math.abs(cosLat), 1e-6);
|
|
321
|
-
const appliedPadding = Math.max(0, paddingRatio);
|
|
322
|
-
const widthWithPadding = dimensions.width * (1 + appliedPadding);
|
|
323
|
-
const depthWithPadding = dimensions.depth * (1 + appliedPadding);
|
|
324
|
-
const halfDepthDegrees = depthWithPadding / 2 / metersPerDegreeLat;
|
|
325
|
-
const halfWidthDegrees = widthWithPadding / 2 / metersPerDegreeLon;
|
|
326
|
-
const clamp = (value, min, max) => Math.min(Math.max(value, min), max);
|
|
327
|
-
const minLat = clamp(center.latitude - halfDepthDegrees, -90, 90);
|
|
328
|
-
const maxLat = clamp(center.latitude + halfDepthDegrees, -90, 90);
|
|
329
|
-
const minLon = clamp(center.longitude - halfWidthDegrees, -180, 180);
|
|
330
|
-
const maxLon = clamp(center.longitude + halfWidthDegrees, -180, 180);
|
|
331
|
-
return { minLat, maxLat, minLon, maxLon };
|
|
332
|
-
}
|
|
333
|
-
async fetchMapboxTexture(center, dimensions, options) {
|
|
334
|
-
const styleId = options.styleId ?? "mapbox/satellite-v9";
|
|
335
|
-
const normalizedStyleId = styleId.replace("mapbox://styles/", "").replace(/^\/+/, "");
|
|
336
|
-
const width = Math.min(
|
|
337
|
-
1280,
|
|
338
|
-
Math.max(1, Math.floor(options.imageWidth ?? 1024))
|
|
339
|
-
);
|
|
340
|
-
const height = Math.min(
|
|
341
|
-
1280,
|
|
342
|
-
Math.max(1, Math.floor(options.imageHeight ?? 1024))
|
|
343
|
-
);
|
|
344
|
-
const highResSuffix = options.highResolution ? "@2x" : "";
|
|
345
|
-
const format = options.imageFormat ?? "png";
|
|
346
|
-
const paddingRatio = options.paddingRatio ?? 0.1;
|
|
347
|
-
const bounds = this.computeBoundingBox(center, dimensions, paddingRatio);
|
|
348
|
-
const bbox = `${bounds.minLon.toFixed(6)},${bounds.minLat.toFixed(
|
|
349
|
-
6
|
|
350
|
-
)},${bounds.maxLon.toFixed(6)},${bounds.maxLat.toFixed(6)}`;
|
|
351
|
-
const params = new URLSearchParams({
|
|
352
|
-
access_token: options.accessToken,
|
|
353
|
-
format
|
|
354
|
-
});
|
|
355
|
-
const requestUrl = `https://api.mapbox.com/styles/v1/${normalizedStyleId}/static/[${bbox}]/${width}x${height}${highResSuffix}?${params.toString()}`;
|
|
356
|
-
const response = await fetch(requestUrl);
|
|
357
|
-
if (!response.ok) {
|
|
358
|
-
throw new Error(
|
|
359
|
-
`Mapbox imagery request failed: ${response.status} ${response.statusText}`
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
const blob = await response.blob();
|
|
363
|
-
if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function") {
|
|
364
|
-
throw new Error(
|
|
365
|
-
"URL.createObjectURL is not available in this environment"
|
|
366
|
-
);
|
|
367
|
-
}
|
|
368
|
-
return URL.createObjectURL(blob);
|
|
369
|
-
}
|
|
370
|
-
releaseGeneratedTexture(url) {
|
|
371
|
-
if (!this.generatedTextureUrls.has(url)) {
|
|
372
|
-
return;
|
|
373
|
-
}
|
|
374
|
-
if (typeof URL !== "undefined" && typeof URL.revokeObjectURL === "function") {
|
|
375
|
-
URL.revokeObjectURL(url);
|
|
376
|
-
}
|
|
377
|
-
this.generatedTextureUrls.delete(url);
|
|
378
|
-
}
|
|
379
|
-
/**
|
|
380
|
-
* Gets the current terrain mesh
|
|
381
|
-
*/
|
|
382
|
-
getMesh() {
|
|
383
|
-
return this.mesh;
|
|
384
|
-
}
|
|
385
|
-
/**
|
|
386
|
-
* Gets the current terrain data
|
|
387
|
-
*/
|
|
388
|
-
getData() {
|
|
389
|
-
return this.currentData;
|
|
390
|
-
}
|
|
391
|
-
/**
|
|
392
|
-
* Checks if terrain is currently loading
|
|
393
|
-
*/
|
|
394
|
-
isTerrainLoading() {
|
|
395
|
-
return this.isLoading;
|
|
396
|
-
}
|
|
397
|
-
/**
|
|
398
|
-
* Disposes of all resources
|
|
399
|
-
*/
|
|
400
|
-
dispose() {
|
|
401
|
-
if (this.mesh) {
|
|
402
|
-
this.scene.remove(this.mesh);
|
|
403
|
-
this.mesh.geometry.dispose();
|
|
404
|
-
if (Array.isArray(this.mesh.material)) {
|
|
405
|
-
this.mesh.material.forEach((mat) => mat.dispose());
|
|
406
|
-
} else {
|
|
407
|
-
this.mesh.material.dispose();
|
|
408
|
-
}
|
|
409
|
-
this.mesh = null;
|
|
410
|
-
}
|
|
411
|
-
this.currentData = null;
|
|
412
|
-
if (this.generatedTextureUrls.size > 0) {
|
|
413
|
-
for (const url of Array.from(this.generatedTextureUrls)) {
|
|
414
|
-
this.releaseGeneratedTexture(url);
|
|
415
|
-
}
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
};
|
|
419
|
-
// Annotate the CommonJS export names for ESM import in node:
|
|
420
|
-
0 && (module.exports = {
|
|
421
|
-
TerrainTool
|
|
422
|
-
});
|
|
1
|
+
"use strict";var R=Object.create;var S=Object.defineProperty;var U=Object.getOwnPropertyDescriptor;var P=Object.getOwnPropertyNames;var O=Object.getPrototypeOf,C=Object.prototype.hasOwnProperty;var H=(n,d)=>{for(var e in d)S(n,e,{get:d[e],enumerable:!0})},y=(n,d,e,a)=>{if(d&&typeof d=="object"||typeof d=="function")for(let t of P(d))!C.call(n,t)&&t!==e&&S(n,t,{get:()=>d[t],enumerable:!(a=U(d,t))||a.enumerable});return n};var G=(n,d,e)=>(e=n!=null?R(O(n)):{},y(d||!n||!n.__esModule?S(e,"default",{value:n,enumerable:!0}):e,n)),I=n=>y(S({},"__esModule",{value:!0}),n);var $={};H($,{TerrainTool:()=>M});module.exports=I($);var c=G(require("three")),M=class extends c.EventDispatcher{constructor(e,a={}){super();this.mesh=null;this.currentData=null;this.isLoading=!1;this.generatedTextureUrls=new Set;this.scene=e,this.widthSegments=a.widthSegments??50,this.depthSegments=a.depthSegments??50,this.elevationScale=a.elevationScale??1,this.baseColor=a.baseColor??9139029,this.wireframe=a.wireframe??!1,this.textureUrl=a.textureUrl,this.mapboxOptions=a.mapbox,this.receiveShadow=a.receiveShadow??!0,this.castShadow=a.castShadow??!0,this.useDemoData=a.useDemoData??!1}setMapboxOptions(e){this.mapboxOptions=e}async loadTerrain(e,a){if(this.isLoading){console.warn("Terrain is already loading");return}this.isLoading=!0,this.dispatchEvent({type:"updateStarted",center:e,dimensions:a});try{let t=await this.fetchElevationData(e,a);this.currentData=t,this.dispatchEvent({type:"dataLoaded",data:t});let o=await this.resolveTexture(e,a);await this.createMesh(t,o)}catch(t){console.error("Error loading terrain:",t),this.dispatchEvent({type:"error",message:"Failed to load terrain",error:t})}finally{this.isLoading=!1}}async updateTerrain(e,a){if(!this.currentData&&!e){console.warn("No current terrain data and no center provided");return}let t=e||this.currentData.center,o=a||this.currentData.dimensions;await this.loadTerrain(t,o)}async fetchElevationData(e,a){if(this.useDemoData)return this.generateDemoData(e,a);let t=[],o=111320,m=111320*Math.max(Math.abs(Math.cos(e.latitude*Math.PI/180)),1e-6),s=a.depth/o,i=a.width/m,h=s/this.depthSegments,u=i/this.widthSegments,p=e.latitude+s/2,l=e.longitude-i/2;for(let g=0;g<=this.depthSegments;g++)for(let f=0;f<=this.widthSegments;f++)t.push({latitude:p-g*h,longitude:l+f*u});let r=await fetch("https://api.open-elevation.com/api/v1/lookup",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({locations:t})});if(!r.ok)throw new Error(`Elevation API error: ${r.statusText}`);let v=(await r.json()).results,x=[],T=1/0,w=-1/0;for(let g=0;g<=this.depthSegments;g++){let f=[];for(let E=0;E<=this.widthSegments;E++){let L=g*(this.widthSegments+1)+E,D=v[L].elevation;f.push(D),T=Math.min(T,D),w=Math.max(w,D)}x.push(f)}return{center:e,dimensions:a,elevations:x,minElevation:T,maxElevation:w}}generateDemoData(e,a){let t=[],o=1/0,m=-1/0,s=e.latitude+e.longitude;for(let i=0;i<=this.depthSegments;i++){let h=[];for(let u=0;u<=this.widthSegments;u++){let p=u/this.widthSegments*4,l=i/this.depthSegments*4,r=0;r+=Math.sin(p+s)*500,r+=Math.cos(l+s)*500,r+=Math.sin(p*2+s)*200,r+=Math.cos(l*2+s)*200,r+=Math.sin(p*4+s)*50,r+=Math.cos(l*4+s)*50,r+=(Math.random()-.5)*30,h.push(r),o=Math.min(o,r),m=Math.max(m,r)}t.push(h)}return{center:e,dimensions:a,elevations:t,minElevation:o,maxElevation:m}}async createMesh(e,a){this.mesh&&(this.scene.remove(this.mesh),this.mesh.geometry.dispose(),Array.isArray(this.mesh.material)?this.mesh.material.forEach(i=>i.dispose()):this.mesh.material.dispose());let t=new c.PlaneGeometry(e.dimensions.width,e.dimensions.depth,this.widthSegments,this.depthSegments),o=t.attributes.position,m=0;for(let i=0;i<=this.depthSegments;i++)for(let h=0;h<=this.widthSegments;h++){let p=(e.elevations[i][h]-e.minElevation)*this.elevationScale;o.setZ(m,p),m++}t.computeVertexNormals();let s;if(a?.url)try{let i=await new c.TextureLoader().loadAsync(a.url);s=new c.MeshStandardMaterial({map:i,wireframe:this.wireframe})}catch(i){console.warn("Failed to load terrain texture, using base color.",i),s=new c.MeshStandardMaterial({color:this.baseColor,wireframe:this.wireframe})}finally{a.revokeOnUse&&this.releaseGeneratedTexture(a.url)}else s=new c.MeshStandardMaterial({color:this.baseColor,wireframe:this.wireframe});this.mesh=new c.Mesh(t,s),this.mesh.rotation.x=-Math.PI/2,this.mesh.receiveShadow=this.receiveShadow,this.mesh.castShadow=this.castShadow,this.scene.add(this.mesh),this.dispatchEvent({type:"meshLoaded",mesh:this.mesh})}async resolveTexture(e,a){if(this.textureUrl)return{url:this.textureUrl,revokeOnUse:!1};if(this.mapboxOptions)try{let t=await this.fetchMapboxTexture(e,a,this.mapboxOptions);return this.generatedTextureUrls.add(t),{url:t,revokeOnUse:!0}}catch(t){console.warn("Failed to fetch Mapbox imagery, falling back to base material.",t);return}}computeBoundingBox(e,a,t){let m=e.latitude*Math.PI/180,s=Math.cos(m),i=111320*Math.max(Math.abs(s),1e-6),h=Math.max(0,t),u=a.width*(1+h),l=a.depth*(1+h)/2/111320,r=u/2/i,b=(g,f,E)=>Math.min(Math.max(g,f),E),v=b(e.latitude-l,-90,90),x=b(e.latitude+l,-90,90),T=b(e.longitude-r,-180,180),w=b(e.longitude+r,-180,180);return{minLat:v,maxLat:x,minLon:T,maxLon:w}}async fetchMapboxTexture(e,a,t){let m=(t.styleId??"mapbox/satellite-v9").replace("mapbox://styles/","").replace(/^\/+/,""),s=Math.min(1280,Math.max(1,Math.floor(t.imageWidth??1024))),i=Math.min(1280,Math.max(1,Math.floor(t.imageHeight??1024))),h=t.highResolution?"@2x":"",u=t.imageFormat??"png",p=t.paddingRatio??.1,l=this.computeBoundingBox(e,a,p),r=`${l.minLon.toFixed(6)},${l.minLat.toFixed(6)},${l.maxLon.toFixed(6)},${l.maxLat.toFixed(6)}`,b=new URLSearchParams({access_token:t.accessToken,format:u}),v=`https://api.mapbox.com/styles/v1/${m}/static/[${r}]/${s}x${i}${h}?${b.toString()}`,x=await fetch(v);if(!x.ok)throw new Error(`Mapbox imagery request failed: ${x.status} ${x.statusText}`);let T=await x.blob();if(typeof URL>"u"||typeof URL.createObjectURL!="function")throw new Error("URL.createObjectURL is not available in this environment");return URL.createObjectURL(T)}releaseGeneratedTexture(e){this.generatedTextureUrls.has(e)&&(typeof URL<"u"&&typeof URL.revokeObjectURL=="function"&&URL.revokeObjectURL(e),this.generatedTextureUrls.delete(e))}getMesh(){return this.mesh}getData(){return this.currentData}isTerrainLoading(){return this.isLoading}dispose(){if(this.mesh&&(this.scene.remove(this.mesh),this.mesh.geometry.dispose(),Array.isArray(this.mesh.material)?this.mesh.material.forEach(e=>e.dispose()):this.mesh.material.dispose(),this.mesh=null),this.currentData=null,this.generatedTextureUrls.size>0)for(let e of Array.from(this.generatedTextureUrls))this.releaseGeneratedTexture(e)}};0&&(module.exports={TerrainTool});
|