@tonybfox/threejs-tools 1.0.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/README.md +321 -0
- package/dist/asset-loader/index.cjs +376 -0
- package/dist/asset-loader/index.cjs.map +1 -0
- package/dist/asset-loader/index.d.mts +101 -0
- package/dist/asset-loader/index.d.ts +101 -0
- package/dist/asset-loader/index.mjs +7 -0
- package/dist/asset-loader/index.mjs.map +1 -0
- package/dist/camera/index.cjs +313 -0
- package/dist/camera/index.cjs.map +1 -0
- package/dist/camera/index.d.mts +82 -0
- package/dist/camera/index.d.ts +82 -0
- package/dist/camera/index.mjs +7 -0
- package/dist/camera/index.mjs.map +1 -0
- package/dist/chunk-5DP6WDB3.mjs +1161 -0
- package/dist/chunk-5DP6WDB3.mjs.map +1 -0
- package/dist/chunk-BJKSICFA.mjs +1579 -0
- package/dist/chunk-BJKSICFA.mjs.map +1 -0
- package/dist/chunk-BYRZCHE7.mjs +277 -0
- package/dist/chunk-BYRZCHE7.mjs.map +1 -0
- package/dist/chunk-EIROAPF7.mjs +387 -0
- package/dist/chunk-EIROAPF7.mjs.map +1 -0
- package/dist/chunk-EQDOX34V.mjs +164 -0
- package/dist/chunk-EQDOX34V.mjs.map +1 -0
- package/dist/chunk-IIAZ2WJJ.mjs +405 -0
- package/dist/chunk-IIAZ2WJJ.mjs.map +1 -0
- package/dist/chunk-L4VIIJZD.mjs +340 -0
- package/dist/chunk-L4VIIJZD.mjs.map +1 -0
- package/dist/chunk-P35QJCOG.mjs +339 -0
- package/dist/chunk-P35QJCOG.mjs.map +1 -0
- package/dist/chunk-R64RVBRM.mjs +394 -0
- package/dist/chunk-R64RVBRM.mjs.map +1 -0
- package/dist/compass/index.cjs +375 -0
- package/dist/compass/index.cjs.map +1 -0
- package/dist/compass/index.d.mts +58 -0
- package/dist/compass/index.d.ts +58 -0
- package/dist/compass/index.mjs +7 -0
- package/dist/compass/index.mjs.map +1 -0
- package/dist/grid/index.cjs +200 -0
- package/dist/grid/index.cjs.map +1 -0
- package/dist/grid/index.d.mts +43 -0
- package/dist/grid/index.d.ts +43 -0
- package/dist/grid/index.mjs +7 -0
- package/dist/grid/index.mjs.map +1 -0
- package/dist/index.cjs +5049 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.mts +13 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.mjs +47 -0
- package/dist/index.mjs.map +1 -0
- package/dist/measurements/index.cjs +1198 -0
- package/dist/measurements/index.cjs.map +1 -0
- package/dist/measurements/index.d.mts +449 -0
- package/dist/measurements/index.d.ts +449 -0
- package/dist/measurements/index.mjs +9 -0
- package/dist/measurements/index.mjs.map +1 -0
- package/dist/sunlight/index.cjs +441 -0
- package/dist/sunlight/index.cjs.map +1 -0
- package/dist/sunlight/index.d.mts +92 -0
- package/dist/sunlight/index.d.ts +92 -0
- package/dist/sunlight/index.mjs +7 -0
- package/dist/sunlight/index.mjs.map +1 -0
- package/dist/terrain/index.cjs +423 -0
- package/dist/terrain/index.cjs.map +1 -0
- package/dist/terrain/index.d.mts +219 -0
- package/dist/terrain/index.d.ts +219 -0
- package/dist/terrain/index.mjs +7 -0
- package/dist/terrain/index.mjs.map +1 -0
- package/dist/transform-controls/index.cjs +1587 -0
- package/dist/transform-controls/index.cjs.map +1 -0
- package/dist/transform-controls/index.d.mts +162 -0
- package/dist/transform-controls/index.d.ts +162 -0
- package/dist/transform-controls/index.mjs +13 -0
- package/dist/transform-controls/index.mjs.map +1 -0
- package/dist/view-helper/index.cjs +430 -0
- package/dist/view-helper/index.cjs.map +1 -0
- package/dist/view-helper/index.d.mts +75 -0
- package/dist/view-helper/index.d.ts +75 -0
- package/dist/view-helper/index.mjs +7 -0
- package/dist/view-helper/index.mjs.map +1 -0
- package/package.json +124 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,423 @@
|
|
|
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
|
+
});
|
|
423
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../packages/terrain/src/index.ts","../../packages/terrain/src/TerrainTool.ts"],"sourcesContent":["export { TerrainTool } from './TerrainTool'\nexport type {\n GeoCoordinates,\n TerrainDimensions,\n TerrainToolOptions,\n TerrainData,\n TerrainToolEvents,\n ElevationPoint,\n} from './TerrainTypes'\n","import * as THREE from 'three'\nimport {\n GeoCoordinates,\n TerrainDimensions,\n TerrainToolOptions,\n TerrainData,\n TerrainToolEvents,\n ElevationPoint,\n MapboxTextureOptions,\n} from './TerrainTypes'\n\ntype TerrainTextureSource = {\n url: string\n revokeOnUse: boolean\n}\n\n/**\n * Terrain mesh creator for Three.js scenes\n * Fetches elevation data from Open-Elevation API and creates 3D terrain meshes\n *\n * @example\n * ```javascript\n * const terrainTool = new TerrainTool(scene, {\n * widthSegments: 100,\n * depthSegments: 100,\n * elevationScale: 2.0,\n * useDemoData: false // Set to true for demo/testing without API\n * })\n *\n * // Listen for events\n * terrainTool.addEventListener('dataLoaded', (e) => {\n * console.log('Terrain data loaded:', e.data)\n * })\n *\n * terrainTool.addEventListener('meshLoaded', (e) => {\n * console.log('Terrain mesh created:', e.mesh)\n * })\n *\n * // Load terrain for a location\n * terrainTool.loadTerrain(\n * { latitude: 36.1699, longitude: -115.1398 }, // Las Vegas\n * { width: 5000, depth: 5000 } // 5km x 5km\n * )\n * ```\n */\nexport class TerrainTool extends THREE.EventDispatcher<TerrainToolEvents> {\n private scene: THREE.Scene\n private mesh: THREE.Mesh | null = null\n private currentData: TerrainData | null = null\n private isLoading: boolean = false\n private mapboxOptions?: MapboxTextureOptions\n private generatedTextureUrls: Set<string> = new Set()\n\n // Configuration\n public widthSegments: number\n public depthSegments: number\n public elevationScale: number\n public baseColor: number\n public wireframe: boolean\n public textureUrl: string | undefined\n public receiveShadow: boolean\n public castShadow: boolean\n public useDemoData: boolean\n\n /**\n * Creates a new TerrainTool instance\n * @param scene - The Three.js scene to add terrain to\n * @param options - Configuration options\n */\n constructor(scene: THREE.Scene, options: TerrainToolOptions = {}) {\n super()\n this.scene = scene\n\n // Apply options with defaults\n this.widthSegments = options.widthSegments ?? 50\n this.depthSegments = options.depthSegments ?? 50\n this.elevationScale = options.elevationScale ?? 1.0\n this.baseColor = options.baseColor ?? 0x8b7355\n this.wireframe = options.wireframe ?? false\n this.textureUrl = options.textureUrl\n this.mapboxOptions = options.mapbox\n this.receiveShadow = options.receiveShadow ?? true\n this.castShadow = options.castShadow ?? true\n this.useDemoData = options.useDemoData ?? false\n }\n\n /**\n * Update Mapbox imagery configuration at runtime\n */\n setMapboxOptions(options?: MapboxTextureOptions): void {\n this.mapboxOptions = options\n }\n\n /**\n * Loads terrain data and creates a mesh\n * @param center - Center coordinates (latitude, longitude)\n * @param dimensions - Terrain dimensions in meters\n */\n async loadTerrain(\n center: GeoCoordinates,\n dimensions: TerrainDimensions\n ): Promise<void> {\n if (this.isLoading) {\n console.warn('Terrain is already loading')\n return\n }\n\n this.isLoading = true\n this.dispatchEvent({\n type: 'updateStarted',\n center,\n dimensions,\n })\n\n try {\n // Fetch elevation data\n const terrainData = await this.fetchElevationData(center, dimensions)\n\n // Store the data\n this.currentData = terrainData\n\n // Dispatch data loaded event\n this.dispatchEvent({\n type: 'dataLoaded',\n data: terrainData,\n })\n\n const textureSource = await this.resolveTexture(center, dimensions)\n\n // Create and add mesh to scene\n await this.createMesh(terrainData, textureSource)\n } catch (error) {\n console.error('Error loading terrain:', error)\n this.dispatchEvent({\n type: 'error',\n message: 'Failed to load terrain',\n error: error as Error,\n })\n } finally {\n this.isLoading = false\n }\n }\n\n /**\n * Updates the terrain with new coordinates and/or dimensions\n * @param center - New center coordinates (optional, keeps current if not provided)\n * @param dimensions - New dimensions (optional, keeps current if not provided)\n */\n async updateTerrain(\n center?: GeoCoordinates,\n dimensions?: TerrainDimensions\n ): Promise<void> {\n if (!this.currentData && !center) {\n console.warn('No current terrain data and no center provided')\n return\n }\n\n const newCenter = center || this.currentData!.center\n const newDimensions = dimensions || this.currentData!.dimensions\n\n await this.loadTerrain(newCenter, newDimensions)\n }\n\n /**\n * Fetches elevation data from Open-Elevation API or generates demo data\n * @private\n */\n private async fetchElevationData(\n center: GeoCoordinates,\n dimensions: TerrainDimensions\n ): Promise<TerrainData> {\n if (this.useDemoData) {\n return this.generateDemoData(center, dimensions)\n }\n\n // Calculate grid of lat/lon points\n const points: { latitude: number; longitude: number }[] = []\n\n // Approximate degrees per meter at this latitude\n const metersPerDegreeLat = 111320\n const metersPerDegreeLon =\n 111320 *\n Math.max(Math.abs(Math.cos((center.latitude * Math.PI) / 180)), 1e-6)\n\n const latRange = dimensions.depth / metersPerDegreeLat\n const lonRange = dimensions.width / metersPerDegreeLon\n\n const latStep = latRange / this.depthSegments\n const lonStep = lonRange / this.widthSegments\n\n const startLat = center.latitude + latRange / 2\n const startLon = center.longitude - lonRange / 2\n\n // Create grid of points\n for (let z = 0; z <= this.depthSegments; z++) {\n for (let x = 0; x <= this.widthSegments; x++) {\n points.push({\n latitude: startLat - z * latStep,\n longitude: startLon + x * lonStep,\n })\n }\n }\n\n // Fetch elevation data from Open-Elevation API\n // Note: This API has rate limits and may not work for large requests\n // In production, you might want to use a different API or cache results\n const response = await fetch(\n 'https://api.open-elevation.com/api/v1/lookup',\n {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n locations: points,\n }),\n }\n )\n\n if (!response.ok) {\n throw new Error(`Elevation API error: ${response.statusText}`)\n }\n\n const data = await response.json()\n const results: ElevationPoint[] = data.results\n\n // Convert to 2D grid\n const elevations: number[][] = []\n let minElevation = Infinity\n let maxElevation = -Infinity\n\n for (let z = 0; z <= this.depthSegments; z++) {\n const row: number[] = []\n for (let x = 0; x <= this.widthSegments; x++) {\n const index = z * (this.widthSegments + 1) + x\n const elevation = results[index].elevation\n row.push(elevation)\n minElevation = Math.min(minElevation, elevation)\n maxElevation = Math.max(maxElevation, elevation)\n }\n elevations.push(row)\n }\n\n return {\n center,\n dimensions,\n elevations,\n minElevation,\n maxElevation,\n }\n }\n\n /**\n * Generates demo terrain data using perlin-like noise\n * @private\n */\n private generateDemoData(\n center: GeoCoordinates,\n dimensions: TerrainDimensions\n ): TerrainData {\n const elevations: number[][] = []\n let minElevation = Infinity\n let maxElevation = -Infinity\n\n // Simple sine-based terrain generation for demo\n const seed = center.latitude + center.longitude\n for (let z = 0; z <= this.depthSegments; z++) {\n const row: number[] = []\n for (let x = 0; x <= this.widthSegments; x++) {\n // Create varied terrain using multiple sine waves\n const nx = (x / this.widthSegments) * 4\n const nz = (z / this.depthSegments) * 4\n\n let elevation = 0\n // Large features\n elevation += Math.sin(nx + seed) * 500\n elevation += Math.cos(nz + seed) * 500\n // Medium features\n elevation += Math.sin(nx * 2 + seed) * 200\n elevation += Math.cos(nz * 2 + seed) * 200\n // Small features\n elevation += Math.sin(nx * 4 + seed) * 50\n elevation += Math.cos(nz * 4 + seed) * 50\n // Random noise\n elevation += (Math.random() - 0.5) * 30\n\n row.push(elevation)\n minElevation = Math.min(minElevation, elevation)\n maxElevation = Math.max(maxElevation, elevation)\n }\n elevations.push(row)\n }\n\n return {\n center,\n dimensions,\n elevations,\n minElevation,\n maxElevation,\n }\n }\n\n /**\n * Creates a terrain mesh from elevation data\n * @private\n */\n private async createMesh(\n data: TerrainData,\n textureSource?: TerrainTextureSource\n ): Promise<void> {\n // Remove existing mesh if any\n if (this.mesh) {\n this.scene.remove(this.mesh)\n this.mesh.geometry.dispose()\n if (Array.isArray(this.mesh.material)) {\n this.mesh.material.forEach((mat) => mat.dispose())\n } else {\n this.mesh.material.dispose()\n }\n }\n\n // Create plane geometry\n const geometry = new THREE.PlaneGeometry(\n data.dimensions.width,\n data.dimensions.depth,\n this.widthSegments,\n this.depthSegments\n )\n\n // Apply elevation data to geometry vertices\n const positions = geometry.attributes.position\n let vertexIndex = 0\n\n for (let z = 0; z <= this.depthSegments; z++) {\n for (let x = 0; x <= this.widthSegments; x++) {\n const elevation = data.elevations[z][x]\n // Normalize elevation relative to min elevation\n const height = (elevation - data.minElevation) * this.elevationScale\n positions.setZ(vertexIndex, height)\n vertexIndex++\n }\n }\n\n // Recompute normals for proper lighting\n geometry.computeVertexNormals()\n\n // Create material\n let material: THREE.Material\n\n if (textureSource?.url) {\n try {\n const texture = await new THREE.TextureLoader().loadAsync(\n textureSource.url\n )\n material = new THREE.MeshStandardMaterial({\n map: texture,\n wireframe: this.wireframe,\n })\n } catch (error) {\n console.warn('Failed to load terrain texture, using base color.', error)\n material = new THREE.MeshStandardMaterial({\n color: this.baseColor,\n wireframe: this.wireframe,\n })\n } finally {\n if (textureSource.revokeOnUse) {\n this.releaseGeneratedTexture(textureSource.url)\n }\n }\n } else {\n material = new THREE.MeshStandardMaterial({\n color: this.baseColor,\n wireframe: this.wireframe,\n })\n }\n\n // Create mesh\n this.mesh = new THREE.Mesh(geometry, material)\n this.mesh.rotation.x = -Math.PI / 2 // Rotate to horizontal\n this.mesh.receiveShadow = this.receiveShadow\n this.mesh.castShadow = this.castShadow\n\n // Add to scene\n this.scene.add(this.mesh)\n\n // Dispatch mesh loaded event\n this.dispatchEvent({\n type: 'meshLoaded',\n mesh: this.mesh,\n })\n }\n\n private async resolveTexture(\n center: GeoCoordinates,\n dimensions: TerrainDimensions\n ): Promise<TerrainTextureSource | undefined> {\n if (this.textureUrl) {\n return {\n url: this.textureUrl,\n revokeOnUse: false,\n }\n }\n\n if (!this.mapboxOptions) {\n return undefined\n }\n\n try {\n const url = await this.fetchMapboxTexture(\n center,\n dimensions,\n this.mapboxOptions\n )\n this.generatedTextureUrls.add(url)\n return {\n url,\n revokeOnUse: true,\n }\n } catch (error) {\n console.warn(\n 'Failed to fetch Mapbox imagery, falling back to base material.',\n error\n )\n return undefined\n }\n }\n\n private computeBoundingBox(\n center: GeoCoordinates,\n dimensions: TerrainDimensions,\n paddingRatio: number\n ): { minLat: number; maxLat: number; minLon: number; maxLon: number } {\n const metersPerDegreeLat = 111320\n const latRadians = (center.latitude * Math.PI) / 180\n const cosLat = Math.cos(latRadians)\n const metersPerDegreeLon = 111320 * Math.max(Math.abs(cosLat), 1e-6)\n\n const appliedPadding = Math.max(0, paddingRatio)\n const widthWithPadding = dimensions.width * (1 + appliedPadding)\n const depthWithPadding = dimensions.depth * (1 + appliedPadding)\n\n const halfDepthDegrees = depthWithPadding / 2 / metersPerDegreeLat\n const halfWidthDegrees = widthWithPadding / 2 / metersPerDegreeLon\n\n const clamp = (value: number, min: number, max: number) =>\n Math.min(Math.max(value, min), max)\n\n const minLat = clamp(center.latitude - halfDepthDegrees, -90, 90)\n const maxLat = clamp(center.latitude + halfDepthDegrees, -90, 90)\n const minLon = clamp(center.longitude - halfWidthDegrees, -180, 180)\n const maxLon = clamp(center.longitude + halfWidthDegrees, -180, 180)\n\n return { minLat, maxLat, minLon, maxLon }\n }\n\n private async fetchMapboxTexture(\n center: GeoCoordinates,\n dimensions: TerrainDimensions,\n options: MapboxTextureOptions\n ): Promise<string> {\n const styleId = options.styleId ?? 'mapbox/satellite-v9'\n const normalizedStyleId = styleId\n .replace('mapbox://styles/', '')\n .replace(/^\\/+/, '')\n\n const width = Math.min(\n 1280,\n Math.max(1, Math.floor(options.imageWidth ?? 1024))\n )\n const height = Math.min(\n 1280,\n Math.max(1, Math.floor(options.imageHeight ?? 1024))\n )\n const highResSuffix = options.highResolution ? '@2x' : ''\n const format = options.imageFormat ?? 'png'\n const paddingRatio = options.paddingRatio ?? 0.1\n\n const bounds = this.computeBoundingBox(center, dimensions, paddingRatio)\n const bbox = `${bounds.minLon.toFixed(6)},${bounds.minLat.toFixed(\n 6\n )},${bounds.maxLon.toFixed(6)},${bounds.maxLat.toFixed(6)}`\n\n const params = new URLSearchParams({\n access_token: options.accessToken,\n format,\n })\n\n const requestUrl = `https://api.mapbox.com/styles/v1/${normalizedStyleId}/static/[${bbox}]/${width}x${height}${highResSuffix}?${params.toString()}`\n\n const response = await fetch(requestUrl)\n if (!response.ok) {\n throw new Error(\n `Mapbox imagery request failed: ${response.status} ${response.statusText}`\n )\n }\n\n const blob = await response.blob()\n\n if (\n typeof URL === 'undefined' ||\n typeof URL.createObjectURL !== 'function'\n ) {\n throw new Error(\n 'URL.createObjectURL is not available in this environment'\n )\n }\n\n return URL.createObjectURL(blob)\n }\n\n private releaseGeneratedTexture(url: string): void {\n if (!this.generatedTextureUrls.has(url)) {\n return\n }\n\n if (\n typeof URL !== 'undefined' &&\n typeof URL.revokeObjectURL === 'function'\n ) {\n URL.revokeObjectURL(url)\n }\n\n this.generatedTextureUrls.delete(url)\n }\n\n /**\n * Gets the current terrain mesh\n */\n getMesh(): THREE.Mesh | null {\n return this.mesh\n }\n\n /**\n * Gets the current terrain data\n */\n getData(): TerrainData | null {\n return this.currentData\n }\n\n /**\n * Checks if terrain is currently loading\n */\n isTerrainLoading(): boolean {\n return this.isLoading\n }\n\n /**\n * Disposes of all resources\n */\n dispose(): void {\n if (this.mesh) {\n this.scene.remove(this.mesh)\n this.mesh.geometry.dispose()\n if (Array.isArray(this.mesh.material)) {\n this.mesh.material.forEach((mat) => mat.dispose())\n } else {\n this.mesh.material.dispose()\n }\n this.mesh = null\n }\n this.currentData = null\n\n if (this.generatedTextureUrls.size > 0) {\n for (const url of Array.from(this.generatedTextureUrls)) {\n this.releaseGeneratedTexture(url)\n }\n }\n }\n}\n"],"mappings":"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","names":[]}
|