@eturnity/eturnity_3d 9.13.0-google3dTile.1 → 9.13.0-google3dTile.2

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.13.0-google3dTile.1",
4
+ "version": "9.13.0-google3dTile.2",
5
5
  "files": [
6
6
  "dist",
7
7
  "src"
@@ -20,20 +20,6 @@ import {
20
20
  TileCompressionPlugin,
21
21
  } from '3d-tiles-renderer/plugins'
22
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
23
  /**
38
24
  * Max screen-space error for tile refinement (3d-tiles-renderer).
39
25
  * Lower = finer meshes for tiles inside {@link Google3DTilesOverlay#_tilesCamera}'s frustum.
@@ -48,7 +34,7 @@ const TILES_FRUSTUM_ERROR_TARGET = 4
48
34
  const GOOGLE_TILES_RENDER_ORDER = 25
49
35
 
50
36
  /** Extra meters padded on each side of roof AABB for tile loading frustum */
51
- const ROOFS_BOUNDS_MARGIN_M = 50
37
+ const ROOFS_BOUNDS_MARGIN_M = 5
52
38
 
53
39
  /** Same rhythm as ThreeDModelOverlay.animatePlaceholder (sin² on time) */
54
40
  function placeholderBounceScale(timeSeconds) {
@@ -76,10 +62,10 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
76
62
  // Dedicated camera for tile loading and resolution so the user's camera does not affect which tiles load
77
63
  // Orthographic camera looking down (Z up), covering -50 to +50 meters in X and Y
78
64
  this._tilesCamera = new THREE.OrthographicCamera(
79
- -50,
80
- 50,
81
- 50,
82
- -50,
65
+ -10,
66
+ 10,
67
+ 10,
68
+ -10,
83
69
  0.1,
84
70
  10000
85
71
  )
@@ -93,18 +79,13 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
93
79
  this._placeholderCenterY = 0
94
80
  this._rayDirDown = new THREE.Vector3(0, 0, 1)
95
81
  this._defaultTilesCamZ = -100
96
- this._defaultOrthoHalfExtentM = 100
97
- const raycaster = new THREE.Raycaster()
98
- raycaster.far = 1000000
99
- raycaster.near = 1
100
- this._defaultRayOrigin = new THREE.Vector3(0, 0, this._defaultTilesCamZ)
101
- raycaster.set(this._defaultRayOrigin, this._rayDirDown)
102
- this._raycaster = raycaster
82
+ this._defaultOrthoHalfExtentM = 10
103
83
  this.sphereMesh = null
104
84
  /** Transparent bouncing sphere at origin; removed once ground align settles */
105
85
  this._groundAlignPlaceholderMesh = null
106
86
  /** True when raycast tile bbox min.z is within ±10 after stick-to-ground */
107
- this._tilesGroundAligned = false
87
+ this.tilesFullyLoaded = false
88
+ this.scene = null
108
89
  }
109
90
  getOverlayMeshForRaycast() {
110
91
  return this.tilesData?.tiles.group
@@ -152,8 +133,6 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
152
133
  cam.lookAt(0, 0, 0)
153
134
  cam.updateProjectionMatrix()
154
135
  cam.updateMatrixWorld(true)
155
- this._defaultRayOrigin.set(0, 0, z)
156
- this._raycaster.set(this._defaultRayOrigin, this._rayDirDown)
157
136
  this._placeholderCenterX = 0
158
137
  this._placeholderCenterY = 0
159
138
  this._syncPlaceholderWorldXY()
@@ -195,8 +174,6 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
195
174
  cam.updateProjectionMatrix()
196
175
  cam.updateMatrixWorld(true)
197
176
 
198
- this._raycaster.set(new THREE.Vector3(cx, cy, z), this._rayDirDown)
199
-
200
177
  this._placeholderCenterX = cx
201
178
  this._placeholderCenterY = cy
202
179
  this._syncPlaceholderWorldXY()
@@ -209,26 +186,6 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
209
186
  mesh.position.y = this._placeholderCenterY
210
187
  }
211
188
 
212
- /**
213
- * Cast a vertical ray at the origin pointing down and return the intersection height.
214
- * @returns {number|null} - The z coordinate of the first intersection, or null if none
215
- */
216
- raycastAtRoofsBoundCenter() {
217
- const { tilesWrapper } = this.tilesData || {}
218
- if (!tilesWrapper) return null
219
-
220
- tilesWrapper.updateMatrixWorld(true)
221
-
222
- const intersects = this._raycaster.intersectObject(tilesWrapper, true)
223
- if (intersects.length > 0) {
224
- if (intersects[0].point.z < 30) {
225
- this._tilesGroundAligned = true
226
- }
227
- return intersects[0]
228
- }
229
- return null
230
- }
231
-
232
189
  _getOrCreateTilesData() {
233
190
  if (this.tilesData) return this.tilesData
234
191
  const apiKey = process.env.VUE_APP_GOOGLE_MAP_API_KEY
@@ -265,12 +222,16 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
265
222
  tilesWrapper.rotation.x = Math.PI / 2
266
223
  tilesWrapper.rotation.y = Math.PI
267
224
  tilesWrapper.add(tiles.group)
268
- tilesWrapper.position.z = -200
225
+ tilesWrapper.position.z = 0
269
226
  this.tilesData = { tiles, tilesWrapper }
270
227
 
271
- tiles.addEventListener('load-model', ({ scene }) => {
272
- applyDoubleSideToTileScene(scene)
273
- this._applyGoogleTilesOpacity(tilesWrapper)
228
+ tiles.addEventListener('load-model', ({ scene, tile }) => {
229
+ console.log('tile size loading', tiles.loadingTiles.size)
230
+ this.tilesFullyLoaded = tiles.loadingTiles.size == 0
231
+ if (this.tilesFullyLoaded) {
232
+ this._applyGoogleTilesShadowProperty(tilesWrapper)
233
+ this.stickObjectToTheGround(scene)
234
+ }
274
235
  })
275
236
 
276
237
  return this.tilesData
@@ -297,11 +258,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
297
258
  const mesh = new THREE.Mesh(geometry, material)
298
259
  mesh.name = 'Google3DTilesGroundAlignPlaceholder'
299
260
  mesh.userData.googleTilesGroundAlignPlaceholder = true
300
- mesh.position.set(
301
- this._placeholderCenterX,
302
- this._placeholderCenterY,
303
- 0
304
- )
261
+ mesh.position.set(this._placeholderCenterX, this._placeholderCenterY, 0)
305
262
  mesh.scale.setScalar(1)
306
263
  scene.add(mesh)
307
264
  this._groundAlignPlaceholderMesh = mesh
@@ -345,49 +302,122 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
345
302
 
346
303
  _syncGroundAlignPlaceholder(threeJSComponent) {
347
304
  if (!threeJSComponent?.scene) return
348
- if (this._tilesGroundAligned) {
305
+ if (this.tilesFullyLoaded) {
349
306
  this._removeGroundAlignPlaceholder(threeJSComponent)
350
307
  } else {
351
308
  this._ensureGroundAlignPlaceholder(threeJSComponent)
352
309
  }
353
310
  }
354
-
355
- stickObjectToTheGround() {
311
+ getBoundingBoxOfTiles(tileGroups) {
312
+ let mergedBoundingBox = new THREE.Box3()
313
+ tileGroups.forEach((tileGroup) => {
314
+ const tileMesh = tileGroup.children[0]
315
+ if (tileGroup.visible && tileMesh.geometry && tileMesh.visible) {
316
+ //create random color
317
+ const boundingBox = this.getBoundingBoxOfTile(tileMesh)
318
+ //compute bounding box in world coordinates
319
+ mergedBoundingBox.union(boundingBox)
320
+ }
321
+ })
322
+ return mergedBoundingBox
323
+ }
324
+ stickObjectToTheGround(scene) {
356
325
  const { tiles, tilesWrapper } = this.tilesData || {}
357
326
  if (!tiles || !tilesWrapper) {
358
327
  this._tilesGroundAligned = false
359
328
  return
360
329
  }
361
330
 
362
- const raycastIntersect = this.raycastAtRoofsBoundCenter()
363
- if (!raycastIntersect) {
364
- this._tilesGroundAligned = false
365
- return
366
- }
367
-
368
- const tileMesh = raycastIntersect.object
369
- const geom = tileMesh.geometry
370
- if (!geom) {
371
- this._tilesGroundAligned = false
372
- return
373
- }
374
- if (!geom.boundingBox) geom.computeBoundingBox()
375
-
376
- const boundingBox = new THREE.Box3()
377
- .copy(geom.boundingBox)
378
- .applyMatrix4(tileMesh.matrixWorld)
379
- if (
380
- Math.abs(boundingBox.min.z) > 1 &&
381
- Math.abs(tilesWrapper.position.z - boundingBox.min.z) < 2000
331
+ //merge all bounding boxes of the tiles
332
+ const boundingBox = this.getBoundingBoxOfTiles(
333
+ tilesWrapper.children[0].children
334
+ )
335
+ tilesWrapper.position.z -= boundingBox.min.z
336
+ tilesWrapper.updateMatrixWorld(true)
337
+ this._tilesGroundAligned = true
338
+ }
339
+ getBoundingBoxOfTile(tileMesh) {
340
+ const extremePoints = []
341
+ let minX = Infinity,
342
+ maxX = -Infinity,
343
+ minY = Infinity,
344
+ maxY = -Infinity,
345
+ minZ = Infinity,
346
+ maxZ = -Infinity
347
+ //use for instead of forEach as the array doesn't contain points but indices
348
+ for (
349
+ let i = 300;
350
+ i < tileMesh.geometry.attributes.position.array.length;
351
+ i += 3
382
352
  ) {
383
- tilesWrapper.position.z -= boundingBox.min.z
353
+ const x = tileMesh.geometry.attributes.position.array[i]
354
+ const y = tileMesh.geometry.attributes.position.array[i + 1]
355
+ const z = tileMesh.geometry.attributes.position.array[i + 2]
356
+ let point = new THREE.Vector3(x, y, z)
357
+ point.applyMatrix4(tileMesh.matrixWorld)
358
+ minX = Math.min(minX, point.x)
359
+ maxX = Math.max(maxX, point.x)
360
+ minY = Math.min(minY, point.y)
361
+ maxY = Math.max(maxY, point.y)
362
+ minZ = Math.min(minZ, point.z)
363
+ maxZ = Math.max(maxZ, point.z)
384
364
  }
385
365
 
386
- tilesWrapper.updateMatrixWorld(true)
366
+ extremePoints.push(new THREE.Vector3(maxX, maxY, maxZ))
367
+ extremePoints.push(new THREE.Vector3(minX, maxY, minZ))
368
+ extremePoints.push(new THREE.Vector3(maxX, minY, minZ))
369
+ extremePoints.push(new THREE.Vector3(minX, maxY, maxZ))
370
+ extremePoints.push(new THREE.Vector3(maxX, minY, maxZ))
371
+ extremePoints.push(new THREE.Vector3(minX, minY, maxZ))
372
+ extremePoints.push(new THREE.Vector3(maxX, maxY, minZ))
373
+
374
+ const boundingBox = new THREE.Box3(
375
+ new THREE.Vector3(minX, minY, minZ),
376
+ new THREE.Vector3(maxX, maxY, maxZ)
377
+ )
378
+ return boundingBox
387
379
  }
380
+
388
381
  /**
389
382
  * Apply global overlay opacity to all tile mesh materials (base Overlay.setOpacity only updates state + emit).
390
383
  */
384
+ /**
385
+ * Replace MeshBasicMaterial with MeshPhongMaterial for lit/shadow rendering.
386
+ * Textures are reused by reference. Caller must re-run TilesFadePlugin material prep after batch swap.
387
+ */
388
+ _convertGoogleTileBasicToPhong(basic) {
389
+ const phong = new THREE.MeshPhongMaterial()
390
+ //THREE.Material.prototype.copy.call(phong, basic)
391
+ phong.color = new THREE.Color(0xffffff)
392
+ phong.map = basic.map
393
+ phong.side = THREE.DoubleSide
394
+ phong.transparent = false
395
+ phong.opacity = 1
396
+ phong.shadowSide = THREE.DoubleSide
397
+ phong.flatShading = true
398
+ // phong.lightMap = basic.lightMap
399
+ // phong.lightMapIntensity = basic.lightMapIntensity
400
+ // phong.aoMap = basic.aoMap
401
+ // phong.aoMapIntensity = basic.aoMapIntensity
402
+ // phong.specularMap = basic.specularMap
403
+ // phong.alphaMap = basic.alphaMap
404
+ // phong.envMap = basic.envMap
405
+ // phong.envMapRotation.copy(basic.envMapRotation)
406
+ // phong.combine = basic.combine
407
+ basic.dispose()
408
+ return phong
409
+ }
410
+
411
+ /**
412
+ * New materials after Basic→Phong swap must be wrapped for TilesFadePlugin dither fade.
413
+ * Uses FADE_TILES_PLUGIN internals (prepareScene); coupled to 3d-tiles-renderer version.
414
+ */
415
+ _reregisterGoogleTilesFadeMaterials(wrapper) {
416
+ const tiles = this.tilesData?.tiles
417
+ const fadePlugin = tiles?.getPluginByName?.('FADE_TILES_PLUGIN')
418
+ fadePlugin?._fadeMaterialManager?.prepareScene?.(wrapper)
419
+ }
420
+
391
421
  _applyGoogleTilesOpacity(wrapper) {
392
422
  if (!wrapper) return
393
423
  const opacity = this.opacity
@@ -411,7 +441,26 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
411
441
  }
412
442
  })
413
443
  }
414
-
444
+ _applyGoogleTilesShadowProperty(wrapper) {
445
+ if (!wrapper) return
446
+ wrapper.traverse((object) => {
447
+ if (!object.isMesh || !object.material) return
448
+ if (Array.isArray(object.material)) {
449
+ object.material = object.material.map((mat) =>
450
+ this._convertGoogleTileBasicToPhong(mat)
451
+ )
452
+ } else {
453
+ object.material = this._convertGoogleTileBasicToPhong(object.material)
454
+ }
455
+ })
456
+ // this._reregisterGoogleTilesFadeMaterials(wrapper)
457
+ wrapper.traverse((object) => {
458
+ if (!object.isMesh) return
459
+ object.castShadow = true
460
+ object.receiveShadow = true
461
+ object.needsUpdate = true
462
+ })
463
+ }
415
464
  setOpacity(opacity) {
416
465
  super.setOpacity(opacity)
417
466
  const wrapper = this.overlayMesh || this.tilesData?.tilesWrapper
@@ -453,6 +502,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
453
502
 
454
503
  const { tiles, tilesWrapper } = tilesData
455
504
  const { renderer, scene } = threeJSComponent
505
+ this.scene = scene
456
506
  tiles.setCamera(this._tilesCamera)
457
507
  tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
458
508
 
@@ -460,9 +510,8 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
460
510
  tilesWrapper.userData.meshId = this.id
461
511
  tilesWrapper.userData.version = this.version
462
512
 
463
- this.stickObjectToTheGround()
464
513
  this._syncGroundAlignPlaceholder(threeJSComponent)
465
- if (this._groundAlignPlaceholderMesh) {
514
+ if (!this.tilesFullyLoaded) {
466
515
  this._tickGroundAlignPlaceholderAnimation()
467
516
  }
468
517
 
@@ -472,9 +521,8 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
472
521
  tiles.setCamera(this._tilesCamera)
473
522
  tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
474
523
  tiles.update()
475
- this.stickObjectToTheGround()
476
524
  this._syncGroundAlignPlaceholder(threeJSComponent)
477
- if (this._groundAlignPlaceholderMesh) {
525
+ if (!this.tilesFullyLoaded) {
478
526
  this._tickGroundAlignPlaceholderAnimation()
479
527
  }
480
528
  }