@eturnity/eturnity_3d 9.13.0-google3dTile.0 → 9.13.0-google3dTile.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/package.json +1 -1
- package/src/Overlay/Google3DTilesOverlay.js +195 -13
package/package.json
CHANGED
|
@@ -41,6 +41,15 @@ function applyDoubleSideToTileScene(scene) {
|
|
|
41
41
|
*/
|
|
42
42
|
const TILES_FRUSTUM_ERROR_TARGET = 4
|
|
43
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Draw after merged base / roofs (renderOrder 10) and edges (15) so transparent
|
|
46
|
+
* tiles blend with geometry already in the color buffer instead of hiding it.
|
|
47
|
+
*/
|
|
48
|
+
const GOOGLE_TILES_RENDER_ORDER = 25
|
|
49
|
+
|
|
50
|
+
/** Extra meters padded on each side of roof AABB for tile loading frustum */
|
|
51
|
+
const ROOFS_BOUNDS_MARGIN_M = 50
|
|
52
|
+
|
|
44
53
|
/** Same rhythm as ThreeDModelOverlay.animatePlaceholder (sin² on time) */
|
|
45
54
|
function placeholderBounceScale(timeSeconds) {
|
|
46
55
|
const baseRadius = 0.65
|
|
@@ -77,12 +86,19 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
77
86
|
this._tilesCamera.position.set(0, 0, -100)
|
|
78
87
|
this._tilesCamera.lookAt(0, 0, 0)
|
|
79
88
|
this._tilesCamera.updateMatrixWorld(true)
|
|
89
|
+
/** @type {{ xMin: number, xMax: number, yMin: number, yMax: number, zMin?: number, zMax?: number } | null} mm — same shape as Vuex getRoofsBounds (three-d-module) */
|
|
90
|
+
this.roofsBound = null
|
|
91
|
+
/** Padded roof-bounds center (m), same as tile camera look-at XY — placeholder sits at (cx, cy, 0) */
|
|
92
|
+
this._placeholderCenterX = 0
|
|
93
|
+
this._placeholderCenterY = 0
|
|
94
|
+
this._rayDirDown = new THREE.Vector3(0, 0, 1)
|
|
95
|
+
this._defaultTilesCamZ = -100
|
|
96
|
+
this._defaultOrthoHalfExtentM = 100
|
|
80
97
|
const raycaster = new THREE.Raycaster()
|
|
81
|
-
const raycasterOrigin = new THREE.Vector3(0, 0, -100)
|
|
82
|
-
const direction = new THREE.Vector3(0, 0, 1)
|
|
83
98
|
raycaster.far = 1000000
|
|
84
99
|
raycaster.near = 1
|
|
85
|
-
|
|
100
|
+
this._defaultRayOrigin = new THREE.Vector3(0, 0, this._defaultTilesCamZ)
|
|
101
|
+
raycaster.set(this._defaultRayOrigin, this._rayDirDown)
|
|
86
102
|
this._raycaster = raycaster
|
|
87
103
|
this.sphereMesh = null
|
|
88
104
|
/** Transparent bouncing sphere at origin; removed once ground align settles */
|
|
@@ -98,11 +114,106 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
98
114
|
return true
|
|
99
115
|
}
|
|
100
116
|
|
|
117
|
+
_roofsBoundsAreEqual(a, b) {
|
|
118
|
+
if (a === b) return true
|
|
119
|
+
if (!a || !b) return false
|
|
120
|
+
return (
|
|
121
|
+
a.xMin === b.xMin &&
|
|
122
|
+
a.xMax === b.xMax &&
|
|
123
|
+
a.yMin === b.yMin &&
|
|
124
|
+
a.yMax === b.yMax
|
|
125
|
+
)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Store roof AABB (mm) and fit {@link #_tilesCamera} to it (ortho frustum in m).
|
|
130
|
+
* Bounds shape matches Vuex `getRoofsBounds` in `three-d-module.js`.
|
|
131
|
+
* Camera sits above the bounds center; left/right/top/bottom are set so the world XY
|
|
132
|
+
* footprint matches [xMin,xMax]×[yMin,yMax] in meters (same convention as ThreeCanvas orthographic).
|
|
133
|
+
*
|
|
134
|
+
* @param {{ xMin: number, xMax: number, yMin: number, yMax: number, zMin?: number, zMax?: number } | null | undefined} boundsMm
|
|
135
|
+
*/
|
|
136
|
+
updateRoofsBound(boundsMm) {
|
|
137
|
+
this._tilesGroundAligned = false
|
|
138
|
+
if (
|
|
139
|
+
!boundsMm ||
|
|
140
|
+
!Number.isFinite(boundsMm.xMin) ||
|
|
141
|
+
boundsMm.xMin === Infinity
|
|
142
|
+
) {
|
|
143
|
+
this.roofsBound = null
|
|
144
|
+
const h = this._defaultOrthoHalfExtentM
|
|
145
|
+
const cam = this._tilesCamera
|
|
146
|
+
const z = this._defaultTilesCamZ
|
|
147
|
+
cam.position.set(0, 0, z)
|
|
148
|
+
cam.left = -h
|
|
149
|
+
cam.right = h
|
|
150
|
+
cam.top = h
|
|
151
|
+
cam.bottom = -h
|
|
152
|
+
cam.lookAt(0, 0, 0)
|
|
153
|
+
cam.updateProjectionMatrix()
|
|
154
|
+
cam.updateMatrixWorld(true)
|
|
155
|
+
this._defaultRayOrigin.set(0, 0, z)
|
|
156
|
+
this._raycaster.set(this._defaultRayOrigin, this._rayDirDown)
|
|
157
|
+
this._placeholderCenterX = 0
|
|
158
|
+
this._placeholderCenterY = 0
|
|
159
|
+
this._syncPlaceholderWorldXY()
|
|
160
|
+
return
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
this.roofsBound = {
|
|
164
|
+
xMin: boundsMm.xMin,
|
|
165
|
+
xMax: boundsMm.xMax,
|
|
166
|
+
yMin: boundsMm.yMin,
|
|
167
|
+
yMax: boundsMm.yMax,
|
|
168
|
+
zMin: boundsMm.zMin,
|
|
169
|
+
zMax: boundsMm.zMax,
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const m = ROOFS_BOUNDS_MARGIN_M
|
|
173
|
+
const xMinM = boundsMm.xMin / 1000 - m
|
|
174
|
+
const xMaxM = boundsMm.xMax / 1000 + m
|
|
175
|
+
const yMinM = boundsMm.yMin / 1000 - m
|
|
176
|
+
const yMaxM = boundsMm.yMax / 1000 + m
|
|
177
|
+
|
|
178
|
+
const cx = (xMinM + xMaxM) / 2
|
|
179
|
+
const cy = (yMinM + yMaxM) / 2
|
|
180
|
+
|
|
181
|
+
let halfW = (xMaxM - xMinM) / 2
|
|
182
|
+
let halfH = (yMaxM - yMinM) / 2
|
|
183
|
+
const minHalf = 100
|
|
184
|
+
if (halfW < minHalf) halfW = minHalf
|
|
185
|
+
if (halfH < minHalf) halfH = minHalf
|
|
186
|
+
|
|
187
|
+
const cam = this._tilesCamera
|
|
188
|
+
const z = this._defaultTilesCamZ
|
|
189
|
+
cam.position.set(cx, cy, z)
|
|
190
|
+
cam.left = xMinM - cx
|
|
191
|
+
cam.right = xMaxM - cx
|
|
192
|
+
cam.top = yMaxM - cy
|
|
193
|
+
cam.bottom = yMinM - cy
|
|
194
|
+
cam.lookAt(cx, cy, 0)
|
|
195
|
+
cam.updateProjectionMatrix()
|
|
196
|
+
cam.updateMatrixWorld(true)
|
|
197
|
+
|
|
198
|
+
this._raycaster.set(new THREE.Vector3(cx, cy, z), this._rayDirDown)
|
|
199
|
+
|
|
200
|
+
this._placeholderCenterX = cx
|
|
201
|
+
this._placeholderCenterY = cy
|
|
202
|
+
this._syncPlaceholderWorldXY()
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_syncPlaceholderWorldXY() {
|
|
206
|
+
const mesh = this._groundAlignPlaceholderMesh
|
|
207
|
+
if (!mesh) return
|
|
208
|
+
mesh.position.x = this._placeholderCenterX
|
|
209
|
+
mesh.position.y = this._placeholderCenterY
|
|
210
|
+
}
|
|
211
|
+
|
|
101
212
|
/**
|
|
102
213
|
* Cast a vertical ray at the origin pointing down and return the intersection height.
|
|
103
214
|
* @returns {number|null} - The z coordinate of the first intersection, or null if none
|
|
104
215
|
*/
|
|
105
|
-
|
|
216
|
+
raycastAtRoofsBoundCenter() {
|
|
106
217
|
const { tilesWrapper } = this.tilesData || {}
|
|
107
218
|
if (!tilesWrapper) return null
|
|
108
219
|
|
|
@@ -144,21 +255,24 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
144
255
|
// Prefer loading tiles that intersect _tilesCamera before prefetch / out-of-frustum work
|
|
145
256
|
tiles.optimizedLoadStrategy = true
|
|
146
257
|
|
|
147
|
-
tiles.addEventListener('load-model', ({ scene }) => {
|
|
148
|
-
applyDoubleSideToTileScene(scene)
|
|
149
|
-
})
|
|
150
|
-
|
|
151
258
|
tiles.lruCache.minSize = 400
|
|
152
259
|
tiles.lruCache.maxSize = 800
|
|
153
260
|
tiles.parseQueue.maxJobs = 4
|
|
154
261
|
|
|
155
262
|
const tilesWrapper = new THREE.Group()
|
|
156
263
|
tilesWrapper.name = 'Google3DTilesOverlay'
|
|
264
|
+
tilesWrapper.renderOrder = GOOGLE_TILES_RENDER_ORDER
|
|
157
265
|
tilesWrapper.rotation.x = Math.PI / 2
|
|
158
266
|
tilesWrapper.rotation.y = Math.PI
|
|
159
267
|
tilesWrapper.add(tiles.group)
|
|
160
268
|
tilesWrapper.position.z = -200
|
|
161
269
|
this.tilesData = { tiles, tilesWrapper }
|
|
270
|
+
|
|
271
|
+
tiles.addEventListener('load-model', ({ scene }) => {
|
|
272
|
+
applyDoubleSideToTileScene(scene)
|
|
273
|
+
this._applyGoogleTilesOpacity(tilesWrapper)
|
|
274
|
+
})
|
|
275
|
+
|
|
162
276
|
return this.tilesData
|
|
163
277
|
}
|
|
164
278
|
|
|
@@ -183,7 +297,11 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
183
297
|
const mesh = new THREE.Mesh(geometry, material)
|
|
184
298
|
mesh.name = 'Google3DTilesGroundAlignPlaceholder'
|
|
185
299
|
mesh.userData.googleTilesGroundAlignPlaceholder = true
|
|
186
|
-
mesh.position.set(
|
|
300
|
+
mesh.position.set(
|
|
301
|
+
this._placeholderCenterX,
|
|
302
|
+
this._placeholderCenterY,
|
|
303
|
+
0
|
|
304
|
+
)
|
|
187
305
|
mesh.scale.setScalar(1)
|
|
188
306
|
scene.add(mesh)
|
|
189
307
|
this._groundAlignPlaceholderMesh = mesh
|
|
@@ -204,10 +322,10 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
204
322
|
const t = performance.now() * 0.001
|
|
205
323
|
const scale = placeholderBounceScale(t)
|
|
206
324
|
mesh.scale.setScalar(scale)
|
|
325
|
+
mesh.position.x = this._placeholderCenterX
|
|
326
|
+
mesh.position.y = this._placeholderCenterY
|
|
207
327
|
// Light Z bob so it reads as a bounce (Z up)
|
|
208
328
|
mesh.position.z = 0.2 * Math.sin(t * 1.4 * Math.PI) ** 2
|
|
209
|
-
mesh.position.x = 0
|
|
210
|
-
mesh.position.y = 0
|
|
211
329
|
mesh.material.opacity = 0.06 + 0.22 * Math.sin(t * 0.85 * Math.PI) ** 2
|
|
212
330
|
}
|
|
213
331
|
|
|
@@ -241,7 +359,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
241
359
|
return
|
|
242
360
|
}
|
|
243
361
|
|
|
244
|
-
const raycastIntersect = this.
|
|
362
|
+
const raycastIntersect = this.raycastAtRoofsBoundCenter()
|
|
245
363
|
if (!raycastIntersect) {
|
|
246
364
|
this._tilesGroundAligned = false
|
|
247
365
|
return
|
|
@@ -267,7 +385,69 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
267
385
|
|
|
268
386
|
tilesWrapper.updateMatrixWorld(true)
|
|
269
387
|
}
|
|
388
|
+
/**
|
|
389
|
+
* Apply global overlay opacity to all tile mesh materials (base Overlay.setOpacity only updates state + emit).
|
|
390
|
+
*/
|
|
391
|
+
_applyGoogleTilesOpacity(wrapper) {
|
|
392
|
+
if (!wrapper) return
|
|
393
|
+
const opacity = this.opacity
|
|
394
|
+
const isTransparent = opacity < 1 - 1e-6
|
|
395
|
+
wrapper.renderOrder = GOOGLE_TILES_RENDER_ORDER
|
|
396
|
+
wrapper.visible = Boolean(this.isActive) && opacity > 0
|
|
397
|
+
wrapper.traverse((object) => {
|
|
398
|
+
if (!object.isMesh || !object.material) return
|
|
399
|
+
object.renderOrder = GOOGLE_TILES_RENDER_ORDER
|
|
400
|
+
const materials = Array.isArray(object.material)
|
|
401
|
+
? object.material
|
|
402
|
+
: [object.material]
|
|
403
|
+
for (const mat of materials) {
|
|
404
|
+
if (mat) {
|
|
405
|
+
mat.transparent = isTransparent
|
|
406
|
+
mat.opacity = opacity
|
|
407
|
+
// Avoid writing depth when semi-transparent so merged base (base.js) and
|
|
408
|
+
// other geometry drawn earlier remain visible through the tiles.
|
|
409
|
+
mat.depthWrite = !isTransparent
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
})
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
setOpacity(opacity) {
|
|
416
|
+
super.setOpacity(opacity)
|
|
417
|
+
const wrapper = this.overlayMesh || this.tilesData?.tilesWrapper
|
|
418
|
+
if (wrapper) {
|
|
419
|
+
this._applyGoogleTilesOpacity(wrapper)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
setIsActive(isActive) {
|
|
424
|
+
this.isActive = Boolean(isActive)
|
|
425
|
+
const wrapper = this.overlayMesh || this.tilesData?.tilesWrapper
|
|
426
|
+
if (wrapper) {
|
|
427
|
+
this._applyGoogleTilesOpacity(wrapper)
|
|
428
|
+
}
|
|
429
|
+
this.emit('item-updated', this)
|
|
430
|
+
}
|
|
431
|
+
|
|
270
432
|
async renderOnThreeJS(threeJSComponent) {
|
|
433
|
+
const rb = threeJSComponent?.roofsBounds
|
|
434
|
+
if (rb && !this._roofsBoundsAreEqual(this.roofsBound, rb)) {
|
|
435
|
+
this.updateRoofsBound(rb)
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
if (!this.isActive) {
|
|
439
|
+
this._removeGroundAlignPlaceholder(threeJSComponent)
|
|
440
|
+
const tilesWrapper =
|
|
441
|
+
this.tilesData?.tilesWrapper ||
|
|
442
|
+
threeJSComponent.meshes.overlayMeshes[this.id]
|
|
443
|
+
if (tilesWrapper) {
|
|
444
|
+
tilesWrapper.visible = false
|
|
445
|
+
threeJSComponent.meshes.overlayMeshes[this.id] = tilesWrapper
|
|
446
|
+
this.overlayMesh = tilesWrapper
|
|
447
|
+
}
|
|
448
|
+
return tilesWrapper || null
|
|
449
|
+
}
|
|
450
|
+
|
|
271
451
|
const tilesData = this._getOrCreateTilesData()
|
|
272
452
|
if (!tilesData) return null
|
|
273
453
|
|
|
@@ -287,7 +467,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
287
467
|
}
|
|
288
468
|
|
|
289
469
|
tilesWrapper.userData.update = () => {
|
|
290
|
-
if (!tiles || !renderer) return
|
|
470
|
+
if (!this.isActive || !tiles || !renderer) return
|
|
291
471
|
tiles.errorTarget = TILES_FRUSTUM_ERROR_TARGET
|
|
292
472
|
tiles.setCamera(this._tilesCamera)
|
|
293
473
|
tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
|
|
@@ -306,6 +486,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
306
486
|
|
|
307
487
|
threeJSComponent.meshes.overlayMeshes[this.id] = tilesWrapper
|
|
308
488
|
this.overlayMesh = tilesWrapper
|
|
489
|
+
this._applyGoogleTilesOpacity(tilesWrapper)
|
|
309
490
|
return tilesWrapper
|
|
310
491
|
}
|
|
311
492
|
|
|
@@ -338,6 +519,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
|
|
|
338
519
|
}
|
|
339
520
|
this.tilesData = null
|
|
340
521
|
this.overlayMesh = null
|
|
522
|
+
this.roofsBound = null
|
|
341
523
|
this._initPromise = null
|
|
342
524
|
this._tilesGroundAligned = false
|
|
343
525
|
}
|