@eturnity/eturnity_3d 9.10.1 → 9.13.0-google3dTile.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/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@eturnity/eturnity_3d",
|
|
3
3
|
"private": false,
|
|
4
|
-
"version": "9.
|
|
4
|
+
"version": "9.13.0-google3dTile.0",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
7
7
|
"src"
|
|
@@ -16,8 +16,9 @@
|
|
|
16
16
|
"merge-remote-master": "node scripts/merge-remote-master.js"
|
|
17
17
|
},
|
|
18
18
|
"dependencies": {
|
|
19
|
-
"@eturnity/eturnity_maths": "9.
|
|
19
|
+
"@eturnity/eturnity_maths": "9.13.0",
|
|
20
20
|
"@originjs/vite-plugin-commonjs": "1.0.3",
|
|
21
|
+
"3d-tiles-renderer": "^0.4.22",
|
|
21
22
|
"core-js": "3.30.2",
|
|
22
23
|
"cors": "2.8.5",
|
|
23
24
|
"earcut": "2.2.4",
|
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Google 3D Tiles Overlay
|
|
3
|
+
*
|
|
4
|
+
* Loads 3D Tiles from Google's Photorealistic 3D Tiles API and displays them
|
|
5
|
+
* in the Three.js scene. The scene origin is at (centerLat, centerLon) at ground level,
|
|
6
|
+
* with an ENU coordinate system: X = East, Y = North, Z = Up, in meters.
|
|
7
|
+
*
|
|
8
|
+
* Integrates with the existing Overlay system so it can be rendered via renderOnThreeJS
|
|
9
|
+
* and updated every frame via userData.update on the mesh.
|
|
10
|
+
* Extends ThreeDModelOverlay to reuse getProjectedPlanesOnOverlay for roof projection.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import ThreeDModelOverlay from './ThreeDModelOverlay'
|
|
14
|
+
import * as THREE from 'three'
|
|
15
|
+
import { TilesRenderer } from '3d-tiles-renderer'
|
|
16
|
+
import {
|
|
17
|
+
TilesFadePlugin,
|
|
18
|
+
GoogleCloudAuthPlugin,
|
|
19
|
+
ReorientationPlugin,
|
|
20
|
+
TileCompressionPlugin,
|
|
21
|
+
} from '3d-tiles-renderer/plugins'
|
|
22
|
+
|
|
23
|
+
/** Set DoubleSide on every mesh material in a loaded 3D Tiles scene */
|
|
24
|
+
function applyDoubleSideToTileScene(scene) {
|
|
25
|
+
if (!scene) return
|
|
26
|
+
scene.traverse((object) => {
|
|
27
|
+
if (!object.isMesh || !object.material) return
|
|
28
|
+
const materials = Array.isArray(object.material)
|
|
29
|
+
? object.material
|
|
30
|
+
: [object.material]
|
|
31
|
+
for (const mat of materials) {
|
|
32
|
+
if (mat) mat.side = THREE.DoubleSide
|
|
33
|
+
}
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Max screen-space error for tile refinement (3d-tiles-renderer).
|
|
39
|
+
* Lower = finer meshes for tiles inside {@link Google3DTilesOverlay#_tilesCamera}'s frustum.
|
|
40
|
+
* GoogleCloudAuthPlugin sets 20 for broad views; we override for this fixed ortho footprint.
|
|
41
|
+
*/
|
|
42
|
+
const TILES_FRUSTUM_ERROR_TARGET = 4
|
|
43
|
+
|
|
44
|
+
/** Same rhythm as ThreeDModelOverlay.animatePlaceholder (sin² on time) */
|
|
45
|
+
function placeholderBounceScale(timeSeconds) {
|
|
46
|
+
const baseRadius = 0.65
|
|
47
|
+
const amplitude = 0.55
|
|
48
|
+
return baseRadius + amplitude * Math.sin(timeSeconds * 0.7 * Math.PI) ** 2
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
52
|
+
constructor(overlay, emit, origin) {
|
|
53
|
+
super(overlay, emit, origin || {}, null)
|
|
54
|
+
this.hasToBeSavedInBE = false
|
|
55
|
+
this.origin = origin || {}
|
|
56
|
+
this.tilesData = null
|
|
57
|
+
this._initPromise = null
|
|
58
|
+
// Center for ReorientationPlugin: use settings or origin (degrees → radians)
|
|
59
|
+
const lat = this.settings?.centerLat ?? this.origin?.lat
|
|
60
|
+
const lon = this.settings?.centerLon ?? this.origin?.lng
|
|
61
|
+
this._centerLatRad =
|
|
62
|
+
lat != null ? lat * (Math.PI / 180) : 35.6586 * (Math.PI / 180)
|
|
63
|
+
this._centerLonRad =
|
|
64
|
+
lon != null ? lon * (Math.PI / 180) : 139.7454 * (Math.PI / 180)
|
|
65
|
+
// Ground elevation in meters from Google Elevation API (used to offset tiles to ground level)
|
|
66
|
+
this._groundElevation = this.settings?.groundElevation ?? null
|
|
67
|
+
// Dedicated camera for tile loading and resolution so the user's camera does not affect which tiles load
|
|
68
|
+
// Orthographic camera looking down (Z up), covering -50 to +50 meters in X and Y
|
|
69
|
+
this._tilesCamera = new THREE.OrthographicCamera(
|
|
70
|
+
-50,
|
|
71
|
+
50,
|
|
72
|
+
50,
|
|
73
|
+
-50,
|
|
74
|
+
0.1,
|
|
75
|
+
10000
|
|
76
|
+
)
|
|
77
|
+
this._tilesCamera.position.set(0, 0, -100)
|
|
78
|
+
this._tilesCamera.lookAt(0, 0, 0)
|
|
79
|
+
this._tilesCamera.updateMatrixWorld(true)
|
|
80
|
+
const raycaster = new THREE.Raycaster()
|
|
81
|
+
const raycasterOrigin = new THREE.Vector3(0, 0, -100)
|
|
82
|
+
const direction = new THREE.Vector3(0, 0, 1)
|
|
83
|
+
raycaster.far = 1000000
|
|
84
|
+
raycaster.near = 1
|
|
85
|
+
raycaster.set(raycasterOrigin, direction)
|
|
86
|
+
this._raycaster = raycaster
|
|
87
|
+
this.sphereMesh = null
|
|
88
|
+
/** Transparent bouncing sphere at origin; removed once ground align settles */
|
|
89
|
+
this._groundAlignPlaceholderMesh = null
|
|
90
|
+
/** True when raycast tile bbox min.z is within ±10 after stick-to-ground */
|
|
91
|
+
this._tilesGroundAligned = false
|
|
92
|
+
}
|
|
93
|
+
getOverlayMeshForRaycast() {
|
|
94
|
+
return this.tilesData?.tiles.group
|
|
95
|
+
}
|
|
96
|
+
async initialiseModel() {
|
|
97
|
+
this.isReady = true
|
|
98
|
+
return true
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Cast a vertical ray at the origin pointing down and return the intersection height.
|
|
103
|
+
* @returns {number|null} - The z coordinate of the first intersection, or null if none
|
|
104
|
+
*/
|
|
105
|
+
raycastAtOrigin() {
|
|
106
|
+
const { tilesWrapper } = this.tilesData || {}
|
|
107
|
+
if (!tilesWrapper) return null
|
|
108
|
+
|
|
109
|
+
tilesWrapper.updateMatrixWorld(true)
|
|
110
|
+
|
|
111
|
+
const intersects = this._raycaster.intersectObject(tilesWrapper, true)
|
|
112
|
+
if (intersects.length > 0) {
|
|
113
|
+
if (intersects[0].point.z < 30) {
|
|
114
|
+
this._tilesGroundAligned = true
|
|
115
|
+
}
|
|
116
|
+
return intersects[0]
|
|
117
|
+
}
|
|
118
|
+
return null
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
_getOrCreateTilesData() {
|
|
122
|
+
if (this.tilesData) return this.tilesData
|
|
123
|
+
const apiKey = process.env.VUE_APP_GOOGLE_MAP_API_KEY
|
|
124
|
+
const tiles = new TilesRenderer()
|
|
125
|
+
tiles.registerPlugin(
|
|
126
|
+
new GoogleCloudAuthPlugin({
|
|
127
|
+
apiToken: apiKey,
|
|
128
|
+
sessionOptions: null,
|
|
129
|
+
autoRefreshToken: true,
|
|
130
|
+
useRecommendedSettings: true,
|
|
131
|
+
})
|
|
132
|
+
)
|
|
133
|
+
tiles.registerPlugin(
|
|
134
|
+
new ReorientationPlugin({
|
|
135
|
+
lat: this._centerLatRad,
|
|
136
|
+
lon: this._centerLonRad,
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
tiles.registerPlugin(new TileCompressionPlugin())
|
|
140
|
+
tiles.registerPlugin(new TilesFadePlugin({ maximumFadeOutTiles: 50 }))
|
|
141
|
+
|
|
142
|
+
// After GoogleCloudAuthPlugin.init (errorTarget 20), tighten LOD for the ortho frustum only
|
|
143
|
+
tiles.errorTarget = TILES_FRUSTUM_ERROR_TARGET
|
|
144
|
+
// Prefer loading tiles that intersect _tilesCamera before prefetch / out-of-frustum work
|
|
145
|
+
tiles.optimizedLoadStrategy = true
|
|
146
|
+
|
|
147
|
+
tiles.addEventListener('load-model', ({ scene }) => {
|
|
148
|
+
applyDoubleSideToTileScene(scene)
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
tiles.lruCache.minSize = 400
|
|
152
|
+
tiles.lruCache.maxSize = 800
|
|
153
|
+
tiles.parseQueue.maxJobs = 4
|
|
154
|
+
|
|
155
|
+
const tilesWrapper = new THREE.Group()
|
|
156
|
+
tilesWrapper.name = 'Google3DTilesOverlay'
|
|
157
|
+
tilesWrapper.rotation.x = Math.PI / 2
|
|
158
|
+
tilesWrapper.rotation.y = Math.PI
|
|
159
|
+
tilesWrapper.add(tiles.group)
|
|
160
|
+
tilesWrapper.position.z = -200
|
|
161
|
+
this.tilesData = { tiles, tilesWrapper }
|
|
162
|
+
return this.tilesData
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
_ensureGroundAlignPlaceholder(threeJSComponent) {
|
|
166
|
+
if (this._tilesGroundAligned || !threeJSComponent?.scene) return
|
|
167
|
+
const { scene } = threeJSComponent
|
|
168
|
+
if (this._groundAlignPlaceholderMesh) {
|
|
169
|
+
if (scene.getObjectById(this._groundAlignPlaceholderMesh.id)) return
|
|
170
|
+
this._groundAlignPlaceholderMesh.geometry?.dispose()
|
|
171
|
+
if (this._groundAlignPlaceholderMesh.material?.dispose) {
|
|
172
|
+
this._groundAlignPlaceholderMesh.material.dispose()
|
|
173
|
+
}
|
|
174
|
+
this._groundAlignPlaceholderMesh = null
|
|
175
|
+
}
|
|
176
|
+
const geometry = new THREE.SphereGeometry(10, 32, 32)
|
|
177
|
+
const material = new THREE.MeshBasicMaterial({
|
|
178
|
+
color: 0xffffff,
|
|
179
|
+
transparent: true,
|
|
180
|
+
opacity: 0.2,
|
|
181
|
+
depthWrite: false,
|
|
182
|
+
})
|
|
183
|
+
const mesh = new THREE.Mesh(geometry, material)
|
|
184
|
+
mesh.name = 'Google3DTilesGroundAlignPlaceholder'
|
|
185
|
+
mesh.userData.googleTilesGroundAlignPlaceholder = true
|
|
186
|
+
mesh.position.set(0, 0, 0)
|
|
187
|
+
mesh.scale.setScalar(1)
|
|
188
|
+
scene.add(mesh)
|
|
189
|
+
this._groundAlignPlaceholderMesh = mesh
|
|
190
|
+
this._tickGroundAlignPlaceholderAnimation()
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
_tickGroundAlignPlaceholderAnimation() {
|
|
194
|
+
const mesh = this._groundAlignPlaceholderMesh
|
|
195
|
+
if (!mesh?.material) return
|
|
196
|
+
|
|
197
|
+
if (this.opacity === 0) {
|
|
198
|
+
mesh.visible = false
|
|
199
|
+
mesh.material.opacity = 0
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
mesh.visible = true
|
|
204
|
+
const t = performance.now() * 0.001
|
|
205
|
+
const scale = placeholderBounceScale(t)
|
|
206
|
+
mesh.scale.setScalar(scale)
|
|
207
|
+
// Light Z bob so it reads as a bounce (Z up)
|
|
208
|
+
mesh.position.z = 0.2 * Math.sin(t * 1.4 * Math.PI) ** 2
|
|
209
|
+
mesh.position.x = 0
|
|
210
|
+
mesh.position.y = 0
|
|
211
|
+
mesh.material.opacity = 0.06 + 0.22 * Math.sin(t * 0.85 * Math.PI) ** 2
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
_removeGroundAlignPlaceholder(threeJSComponent) {
|
|
215
|
+
const mesh = this._groundAlignPlaceholderMesh
|
|
216
|
+
if (!mesh) return
|
|
217
|
+
const scene = threeJSComponent?.scene
|
|
218
|
+
if (scene && scene.getObjectById(mesh.id)) {
|
|
219
|
+
scene.remove(mesh)
|
|
220
|
+
} else {
|
|
221
|
+
mesh.parent?.remove(mesh)
|
|
222
|
+
}
|
|
223
|
+
mesh.geometry?.dispose()
|
|
224
|
+
if (mesh.material?.dispose) mesh.material.dispose()
|
|
225
|
+
this._groundAlignPlaceholderMesh = null
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
_syncGroundAlignPlaceholder(threeJSComponent) {
|
|
229
|
+
if (!threeJSComponent?.scene) return
|
|
230
|
+
if (this._tilesGroundAligned) {
|
|
231
|
+
this._removeGroundAlignPlaceholder(threeJSComponent)
|
|
232
|
+
} else {
|
|
233
|
+
this._ensureGroundAlignPlaceholder(threeJSComponent)
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
stickObjectToTheGround() {
|
|
238
|
+
const { tiles, tilesWrapper } = this.tilesData || {}
|
|
239
|
+
if (!tiles || !tilesWrapper) {
|
|
240
|
+
this._tilesGroundAligned = false
|
|
241
|
+
return
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const raycastIntersect = this.raycastAtOrigin()
|
|
245
|
+
if (!raycastIntersect) {
|
|
246
|
+
this._tilesGroundAligned = false
|
|
247
|
+
return
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const tileMesh = raycastIntersect.object
|
|
251
|
+
const geom = tileMesh.geometry
|
|
252
|
+
if (!geom) {
|
|
253
|
+
this._tilesGroundAligned = false
|
|
254
|
+
return
|
|
255
|
+
}
|
|
256
|
+
if (!geom.boundingBox) geom.computeBoundingBox()
|
|
257
|
+
|
|
258
|
+
const boundingBox = new THREE.Box3()
|
|
259
|
+
.copy(geom.boundingBox)
|
|
260
|
+
.applyMatrix4(tileMesh.matrixWorld)
|
|
261
|
+
if (
|
|
262
|
+
Math.abs(boundingBox.min.z) > 1 &&
|
|
263
|
+
Math.abs(tilesWrapper.position.z - boundingBox.min.z) < 2000
|
|
264
|
+
) {
|
|
265
|
+
tilesWrapper.position.z -= boundingBox.min.z
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
tilesWrapper.updateMatrixWorld(true)
|
|
269
|
+
}
|
|
270
|
+
async renderOnThreeJS(threeJSComponent) {
|
|
271
|
+
const tilesData = this._getOrCreateTilesData()
|
|
272
|
+
if (!tilesData) return null
|
|
273
|
+
|
|
274
|
+
const { tiles, tilesWrapper } = tilesData
|
|
275
|
+
const { renderer, scene } = threeJSComponent
|
|
276
|
+
tiles.setCamera(this._tilesCamera)
|
|
277
|
+
tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
|
|
278
|
+
|
|
279
|
+
tilesWrapper.userData.type = 'overlayMeshes'
|
|
280
|
+
tilesWrapper.userData.meshId = this.id
|
|
281
|
+
tilesWrapper.userData.version = this.version
|
|
282
|
+
|
|
283
|
+
this.stickObjectToTheGround()
|
|
284
|
+
this._syncGroundAlignPlaceholder(threeJSComponent)
|
|
285
|
+
if (this._groundAlignPlaceholderMesh) {
|
|
286
|
+
this._tickGroundAlignPlaceholderAnimation()
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
tilesWrapper.userData.update = () => {
|
|
290
|
+
if (!tiles || !renderer) return
|
|
291
|
+
tiles.errorTarget = TILES_FRUSTUM_ERROR_TARGET
|
|
292
|
+
tiles.setCamera(this._tilesCamera)
|
|
293
|
+
tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
|
|
294
|
+
tiles.update()
|
|
295
|
+
this.stickObjectToTheGround()
|
|
296
|
+
this._syncGroundAlignPlaceholder(threeJSComponent)
|
|
297
|
+
if (this._groundAlignPlaceholderMesh) {
|
|
298
|
+
this._tickGroundAlignPlaceholderAnimation()
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
if (tilesWrapper.parent !== scene) {
|
|
303
|
+
if (tilesWrapper.parent) tilesWrapper.parent.remove(tilesWrapper)
|
|
304
|
+
scene.add(tilesWrapper)
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
threeJSComponent.meshes.overlayMeshes[this.id] = tilesWrapper
|
|
308
|
+
this.overlayMesh = tilesWrapper
|
|
309
|
+
return tilesWrapper
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
renderOnPaperJS() {
|
|
313
|
+
// No 2D representation for 3D Tiles overlay
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
serializeSettings() {
|
|
317
|
+
return {
|
|
318
|
+
...super.serializeSettings(),
|
|
319
|
+
centerLat: this.origin?.lat ?? this.settings?.centerLat,
|
|
320
|
+
centerLon: this.origin?.lng ?? this.settings?.centerLon,
|
|
321
|
+
groundElevation: this._groundElevation,
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
dispose() {
|
|
326
|
+
if (this._groundAlignPlaceholderMesh) {
|
|
327
|
+
this._groundAlignPlaceholderMesh.parent?.remove(
|
|
328
|
+
this._groundAlignPlaceholderMesh
|
|
329
|
+
)
|
|
330
|
+
this._groundAlignPlaceholderMesh.geometry?.dispose()
|
|
331
|
+
if (this._groundAlignPlaceholderMesh.material?.dispose) {
|
|
332
|
+
this._groundAlignPlaceholderMesh.material.dispose()
|
|
333
|
+
}
|
|
334
|
+
this._groundAlignPlaceholderMesh = null
|
|
335
|
+
}
|
|
336
|
+
if (this.tilesData?.tiles) {
|
|
337
|
+
this.tilesData.tiles.dispose()
|
|
338
|
+
}
|
|
339
|
+
this.tilesData = null
|
|
340
|
+
this.overlayMesh = null
|
|
341
|
+
this._initPromise = null
|
|
342
|
+
this._tilesGroundAligned = false
|
|
343
|
+
}
|
|
344
|
+
}
|
|
@@ -2,6 +2,7 @@ import Overlay from './Overlay'
|
|
|
2
2
|
import GLBOverlay from './GLBOverlay'
|
|
3
3
|
import ImageOverlay from './ImageOverlay'
|
|
4
4
|
import YearlySunShadingOverlay from './YearlySunShadingOverlay'
|
|
5
|
+
import Google3DTilesOverlay from './Google3DTilesOverlay'
|
|
5
6
|
|
|
6
7
|
const OverlayFactory = {
|
|
7
8
|
createOverlay(serializedOverlay, emit, origin = { lat: null, lng: null }) {
|
|
@@ -12,6 +13,8 @@ const OverlayFactory = {
|
|
|
12
13
|
overlayInstance = new ImageOverlay(serializedOverlay, emit)
|
|
13
14
|
} else if (serializedOverlay.type == 'yearly_sun_shading') {
|
|
14
15
|
overlayInstance = new YearlySunShadingOverlay(serializedOverlay, emit)
|
|
16
|
+
} else if (serializedOverlay.type == 'google_3d_tiles') {
|
|
17
|
+
overlayInstance = new Google3DTilesOverlay(serializedOverlay, emit, origin)
|
|
15
18
|
} else {
|
|
16
19
|
overlayInstance = new Overlay(serializedOverlay, emit)
|
|
17
20
|
}
|
|
@@ -350,8 +350,24 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
350
350
|
resolve(imageUrl)
|
|
351
351
|
})
|
|
352
352
|
}
|
|
353
|
+
/** Override in subclasses to use a different mesh (e.g. tiles wrapper) or null when not yet ready */
|
|
354
|
+
getOverlayMeshForRaycast() {
|
|
355
|
+
return this.overlayMesh
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/** Override in subclasses to raycast into child meshes (e.g. for Group-based overlays like 3D Tiles) */
|
|
359
|
+
getRaycastRecursive() {
|
|
360
|
+
return false
|
|
361
|
+
}
|
|
362
|
+
|
|
353
363
|
getProjectedPlanesOnOverlay(polygons) {
|
|
364
|
+
const mesh = this.getOverlayMeshForRaycast()
|
|
365
|
+
if (!mesh) {
|
|
366
|
+
return polygons.map(() => null)
|
|
367
|
+
}
|
|
368
|
+
mesh.updateMatrixWorld(true)
|
|
354
369
|
const RayCaster = new THREE.Raycaster()
|
|
370
|
+
const recursive = this.getRaycastRecursive()
|
|
355
371
|
const cloudsPoints = polygons.map((polygon) => {
|
|
356
372
|
const outline = polygon.outline
|
|
357
373
|
const holeOutlines = polygon.holes.map((h) => h.outline)
|
|
@@ -368,8 +384,8 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
368
384
|
let { xMin, xMax, yMin, yMax } = outlineBounds
|
|
369
385
|
const cloudPoints = []
|
|
370
386
|
let stepNum = 8
|
|
371
|
-
for (let i = 1; i < stepNum; i++) {
|
|
372
|
-
for (let j = 1; j < stepNum; j++) {
|
|
387
|
+
for (let i = 1; i < stepNum - 1; i++) {
|
|
388
|
+
for (let j = 1; j < stepNum - 1; j++) {
|
|
373
389
|
const testPoint = {
|
|
374
390
|
x: xMin + (i * (xMax - xMin)) / stepNum,
|
|
375
391
|
y: yMin + (j * (yMax - yMin)) / stepNum,
|
|
@@ -384,7 +400,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
384
400
|
new THREE.Vector3(testPoint.x / 1000, testPoint.y / 1000, 100),
|
|
385
401
|
new THREE.Vector3(0, 0, -1)
|
|
386
402
|
)
|
|
387
|
-
let intersects = RayCaster.intersectObject(
|
|
403
|
+
let intersects = RayCaster.intersectObject(mesh, recursive)
|
|
388
404
|
if (intersects.length > 0) {
|
|
389
405
|
cloudPoints.push(intersects[0].point)
|
|
390
406
|
}
|
|
@@ -744,6 +744,23 @@ export default {
|
|
|
744
744
|
waitingBall.scale.set(scale, scale, scale)
|
|
745
745
|
this.render()
|
|
746
746
|
}
|
|
747
|
+
// Per-frame updates for overlays (e.g. Google 3D Tiles)
|
|
748
|
+
const overlayMeshes = this.meshes?.overlayMeshes
|
|
749
|
+
let overlayUpdated = false
|
|
750
|
+
if (overlayMeshes) {
|
|
751
|
+
Object.values(overlayMeshes).forEach((mesh) => {
|
|
752
|
+
if (
|
|
753
|
+
mesh?.userData?.update &&
|
|
754
|
+
typeof mesh.userData.update === 'function'
|
|
755
|
+
) {
|
|
756
|
+
mesh.userData.update()
|
|
757
|
+
overlayUpdated = true
|
|
758
|
+
}
|
|
759
|
+
})
|
|
760
|
+
}
|
|
761
|
+
if (overlayUpdated) {
|
|
762
|
+
this.render()
|
|
763
|
+
}
|
|
747
764
|
this.animationFrameId = requestAnimationFrame(this.animate)
|
|
748
765
|
},
|
|
749
766
|
},
|