@eturnity/eturnity_3d 9.10.0 → 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/dist/assets/images/panels/longiSolarLR4_60HPH_350M.png +0 -0
- package/dist/assets/images/panels/longiSolarLR4_60HPH_370M.png +0 -0
- package/dist/assets/images/panels/reneSola_Virtus_2_JC320S_24_Bbw copy.png +0 -0
- package/dist/assets/images/panels/reneSola_Virtus_2_JC320S_24_Bbw.png +0 -0
- package/dist/assets/images/panels/uv.png +0 -0
- package/dist/assets/panels/longiSolarLR4_60HPH_350M.png +0 -0
- package/dist/assets/panels/longiSolarLR4_60HPH_370M.png +0 -0
- package/dist/assets/panels/reneSola_Virtus_2_JC320S_24_Bbw.png +0 -0
- package/dist/assets/panels/uv.png +0 -0
- package/dist/assets/theme.js +43 -0
- package/dist/assets/vue.svg +1 -0
- package/dist/main.es.js +43662 -0
- package/dist/main.umd.js +3542 -0
- package/dist/style.css +1 -0
- package/dist/vite.svg +1 -0
- package/package.json +3 -2
- package/src/Overlay/Google3DTilesOverlay.js +344 -0
- package/src/Overlay/OverlayFactory.js +3 -0
- package/src/Overlay/ThreeDModelOverlay.js +19 -5
- package/src/helper/cameraMixin.js +73 -7
- package/src/helper/render/edge.js +1 -1
- package/src/helper/render/geometryHandler.js +7 -1
- package/src/helper/render/mergedGeometry.js +16 -5
- package/src/helper/render/roof.js +7 -3
- package/src/helper/render/shadingMaterial/helper.js +1 -1
- package/src/helper/render/texture.js +1 -1
- package/src/helper/renderMixin.js +8 -0
package/dist/style.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
.canvas3DContainer{height:100%;width:100%}.canvas3DContainer>canvas{height:100%;width:100%}
|
package/dist/vite.svg
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
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
|
}
|
|
@@ -122,7 +122,6 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
122
122
|
return
|
|
123
123
|
}
|
|
124
124
|
group = new this.Paper.Group()
|
|
125
|
-
|
|
126
125
|
group.addChild(this.renderImagePlaceHolder(paperJSComponent))
|
|
127
126
|
group.itemType = 'placeholderOverlays'
|
|
128
127
|
group.type = 'imageOverlays'
|
|
@@ -138,7 +137,6 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
138
137
|
this.hasPlaceholderPath = true
|
|
139
138
|
return
|
|
140
139
|
}
|
|
141
|
-
|
|
142
140
|
if (this.hasPlaceholderPath) {
|
|
143
141
|
if (group) {
|
|
144
142
|
paperJSComponent.clearPath(group)
|
|
@@ -352,8 +350,24 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
352
350
|
resolve(imageUrl)
|
|
353
351
|
})
|
|
354
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
|
+
|
|
355
363
|
getProjectedPlanesOnOverlay(polygons) {
|
|
364
|
+
const mesh = this.getOverlayMeshForRaycast()
|
|
365
|
+
if (!mesh) {
|
|
366
|
+
return polygons.map(() => null)
|
|
367
|
+
}
|
|
368
|
+
mesh.updateMatrixWorld(true)
|
|
356
369
|
const RayCaster = new THREE.Raycaster()
|
|
370
|
+
const recursive = this.getRaycastRecursive()
|
|
357
371
|
const cloudsPoints = polygons.map((polygon) => {
|
|
358
372
|
const outline = polygon.outline
|
|
359
373
|
const holeOutlines = polygon.holes.map((h) => h.outline)
|
|
@@ -370,8 +384,8 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
370
384
|
let { xMin, xMax, yMin, yMax } = outlineBounds
|
|
371
385
|
const cloudPoints = []
|
|
372
386
|
let stepNum = 8
|
|
373
|
-
for (let i = 1; i < stepNum; i++) {
|
|
374
|
-
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++) {
|
|
375
389
|
const testPoint = {
|
|
376
390
|
x: xMin + (i * (xMax - xMin)) / stepNum,
|
|
377
391
|
y: yMin + (j * (yMax - yMin)) / stepNum,
|
|
@@ -386,7 +400,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
386
400
|
new THREE.Vector3(testPoint.x / 1000, testPoint.y / 1000, 100),
|
|
387
401
|
new THREE.Vector3(0, 0, -1)
|
|
388
402
|
)
|
|
389
|
-
let intersects = RayCaster.intersectObject(
|
|
403
|
+
let intersects = RayCaster.intersectObject(mesh, recursive)
|
|
390
404
|
if (intersects.length > 0) {
|
|
391
405
|
cloudPoints.push(intersects[0].point)
|
|
392
406
|
}
|
|
@@ -440,7 +440,10 @@ export default {
|
|
|
440
440
|
this.setTweenTo({ position, target })
|
|
441
441
|
}
|
|
442
442
|
},
|
|
443
|
-
|
|
443
|
+
setCameraGlobalPositionImmediately() {
|
|
444
|
+
this.setCameraGlobalPosition({ duration: 0 })
|
|
445
|
+
},
|
|
446
|
+
setCameraGlobalPosition({ duration = 1500 } = {}) {
|
|
444
447
|
if (this.camera.isOrthographicCamera) {
|
|
445
448
|
this.setOrthographicCameraGlobalPosition()
|
|
446
449
|
} else {
|
|
@@ -466,10 +469,41 @@ export default {
|
|
|
466
469
|
positionVector.z,
|
|
467
470
|
]
|
|
468
471
|
let target = [targetVector.x, targetVector.y, targetVector.z]
|
|
469
|
-
this.setTweenTo({ position, target, fov: newFov })
|
|
472
|
+
this.setTweenTo({ position, target, fov: newFov, duration })
|
|
470
473
|
}
|
|
471
474
|
},
|
|
472
|
-
|
|
475
|
+
setCameraGlobalAlmostVerticalPosition({ duration = 1000 } = {}) {
|
|
476
|
+
let position, target
|
|
477
|
+
if (this.roofs.length != 0) {
|
|
478
|
+
let bounds = this.roofsBounds
|
|
479
|
+
let targetVector = {
|
|
480
|
+
x: (bounds.xMax + bounds.xMin) / 2000,
|
|
481
|
+
y: (bounds.yMax + bounds.yMin) / 2000,
|
|
482
|
+
z: 0,
|
|
483
|
+
}
|
|
484
|
+
let objectRadius_m = Math.max(
|
|
485
|
+
(bounds.xMax - bounds.xMin) / 2000,
|
|
486
|
+
(bounds.yMax - bounds.yMin) / 2000
|
|
487
|
+
)
|
|
488
|
+
|
|
489
|
+
let distance =
|
|
490
|
+
2 *
|
|
491
|
+
(objectRadius_m / Math.sin(((this.camera.fov / 2) * Math.PI) / 180))
|
|
492
|
+
let cameraMoveVector = { x: 0, y: 0, z: distance }
|
|
493
|
+
const positionVector = addVector(targetVector, cameraMoveVector)
|
|
494
|
+
position = [positionVector.x, positionVector.y - 1, positionVector.z]
|
|
495
|
+
target = [targetVector.x, targetVector.y, targetVector.z]
|
|
496
|
+
} else {
|
|
497
|
+
position = [0, -1, 300]
|
|
498
|
+
target = [0, 0, 0]
|
|
499
|
+
}
|
|
500
|
+
this.setTweenTo({
|
|
501
|
+
position,
|
|
502
|
+
target,
|
|
503
|
+
duration,
|
|
504
|
+
})
|
|
505
|
+
},
|
|
506
|
+
setCameraGlobalVerticalPosition(changeCameraTypeAsCallback = true) {
|
|
473
507
|
let position, target
|
|
474
508
|
let newFov = 10
|
|
475
509
|
if (this.roofs.length != 0) {
|
|
@@ -498,10 +532,12 @@ export default {
|
|
|
498
532
|
position,
|
|
499
533
|
target,
|
|
500
534
|
fov: newFov,
|
|
501
|
-
callback:
|
|
535
|
+
callback: changeCameraTypeAsCallback
|
|
536
|
+
? this.turnPerspectiveCameraToOrthogonal
|
|
537
|
+
: undefined,
|
|
502
538
|
})
|
|
503
539
|
},
|
|
504
|
-
setCameraOrthogonalTo(polygon) {
|
|
540
|
+
setCameraOrthogonalTo(polygon, { duration = 1000 } = {}) {
|
|
505
541
|
if (polygon) {
|
|
506
542
|
let polygonRef = polygon
|
|
507
543
|
const targetVector = polygonRef.meanPoint
|
|
@@ -519,7 +555,7 @@ export default {
|
|
|
519
555
|
positionVector.y / 1000 - 1,
|
|
520
556
|
positionVector.z / 1000,
|
|
521
557
|
]
|
|
522
|
-
this.setTweenTo({ position, target })
|
|
558
|
+
this.setTweenTo({ position, target, duration })
|
|
523
559
|
}
|
|
524
560
|
},
|
|
525
561
|
setCameraSideTo(polygon) {
|
|
@@ -553,11 +589,24 @@ export default {
|
|
|
553
589
|
position,
|
|
554
590
|
target,
|
|
555
591
|
fov = 50,
|
|
556
|
-
duration =
|
|
592
|
+
duration = 1500,
|
|
557
593
|
callback = () => {},
|
|
558
594
|
} = payload
|
|
559
595
|
TWEEN.removeAll()
|
|
560
596
|
if (this.camera) {
|
|
597
|
+
if (duration == 0) {
|
|
598
|
+
this.camera.fov = fov
|
|
599
|
+
this.camera.position.set(position[0], position[1], position[2])
|
|
600
|
+
|
|
601
|
+
this.camera.updateProjectionMatrix()
|
|
602
|
+
|
|
603
|
+
this.orbitControl.target.set(target[0], target[1], target[2])
|
|
604
|
+
if (this.orbitControl && this.orbitControl.update) {
|
|
605
|
+
this.orbitControl.update()
|
|
606
|
+
}
|
|
607
|
+
callback()
|
|
608
|
+
return
|
|
609
|
+
}
|
|
561
610
|
const currentTweenVar = {
|
|
562
611
|
t: 0,
|
|
563
612
|
}
|
|
@@ -695,6 +744,23 @@ export default {
|
|
|
695
744
|
waitingBall.scale.set(scale, scale, scale)
|
|
696
745
|
this.render()
|
|
697
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
|
+
}
|
|
698
764
|
this.animationFrameId = requestAnimationFrame(this.animate)
|
|
699
765
|
},
|
|
700
766
|
},
|
|
@@ -263,7 +263,7 @@ export function getBufferWallGeometry(polygon) {
|
|
|
263
263
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
264
264
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
|
|
265
265
|
geometry.computeVertexNormals()
|
|
266
|
-
|
|
266
|
+
geometry.userData.version = 0
|
|
267
267
|
return geometry
|
|
268
268
|
}
|
|
269
269
|
|
|
@@ -321,6 +321,7 @@ export function getBufferObstacleGeometry(obstaclePolygon, roofPolygon) {
|
|
|
321
321
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
322
322
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
|
|
323
323
|
geometry.computeVertexNormals()
|
|
324
|
+
geometry.userData.version = 0
|
|
324
325
|
return geometry
|
|
325
326
|
}
|
|
326
327
|
|
|
@@ -405,6 +406,7 @@ export function getBufferObstacleSideGeometry(obstaclePolygon, roofPolygon) {
|
|
|
405
406
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
406
407
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
|
|
407
408
|
geometry.computeVertexNormals()
|
|
409
|
+
geometry.userData.version = 0
|
|
408
410
|
return geometry
|
|
409
411
|
}
|
|
410
412
|
|
|
@@ -440,6 +442,7 @@ export function getBufferModuleFieldGeometry(moduleFieldPolygon) {
|
|
|
440
442
|
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2))
|
|
441
443
|
|
|
442
444
|
geometry.computeVertexNormals()
|
|
445
|
+
geometry.userData.version = 0
|
|
443
446
|
return geometry
|
|
444
447
|
}
|
|
445
448
|
|
|
@@ -485,6 +488,7 @@ export function getBufferImageOverlayGeometry(corners) {
|
|
|
485
488
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
|
|
486
489
|
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2))
|
|
487
490
|
geometry.computeVertexNormals()
|
|
491
|
+
geometry.userData.version = 0
|
|
488
492
|
return geometry
|
|
489
493
|
}
|
|
490
494
|
export function getPanelOutlineWithHeightOffset(panelPolygon) {
|
|
@@ -525,6 +529,7 @@ export function getBufferPanelGeometry(panelPolygon) {
|
|
|
525
529
|
geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2))
|
|
526
530
|
|
|
527
531
|
geometry.computeVertexNormals()
|
|
532
|
+
geometry.userData.version = 0
|
|
528
533
|
return geometry
|
|
529
534
|
}
|
|
530
535
|
export function updateBufferPanelGeometry(panelPolygon, geometry) {
|
|
@@ -635,6 +640,7 @@ export function getBufferPanelSideGeometry(panelPolygon) {
|
|
|
635
640
|
// itemSize = 3 because there are 3 values (components) per vertex
|
|
636
641
|
geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3))
|
|
637
642
|
geometry.computeVertexNormals()
|
|
643
|
+
geometry.userData.version = 0
|
|
638
644
|
return geometry
|
|
639
645
|
}
|
|
640
646
|
|
|
@@ -6,9 +6,12 @@ export default {
|
|
|
6
6
|
const mergedVertices = []
|
|
7
7
|
const mergedNormals = []
|
|
8
8
|
const mergedUvs = []
|
|
9
|
-
const geometryVersion = mergedGeometry
|
|
9
|
+
const geometryVersion = mergedGeometry?.userData?.version || 0
|
|
10
10
|
let geometryChanged = false
|
|
11
11
|
geometries.forEach((geometry) => {
|
|
12
|
+
if (!geometry || !geometry.attributes?.position || !geometry.attributes?.normal) {
|
|
13
|
+
return
|
|
14
|
+
}
|
|
12
15
|
const positions = geometry.attributes.position.array
|
|
13
16
|
const normals = geometry.attributes.normal.array
|
|
14
17
|
mergedVertices.push(...positions.slice(0, 3 * geometry.drawRange.count))
|
|
@@ -66,6 +69,9 @@ export default {
|
|
|
66
69
|
const mergedUvs = []
|
|
67
70
|
|
|
68
71
|
geometries.forEach((geometry) => {
|
|
72
|
+
if (!geometry || !geometry.attributes?.position || !geometry.attributes?.normal) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
69
75
|
const positions = geometry.attributes.position.array
|
|
70
76
|
const normals = geometry.attributes.normal.array
|
|
71
77
|
|
|
@@ -96,11 +102,16 @@ export default {
|
|
|
96
102
|
},
|
|
97
103
|
|
|
98
104
|
updateOrCreateMergedMesh(mesh, geometries, material) {
|
|
99
|
-
if (!mesh) {
|
|
105
|
+
if (!mesh || !mesh.geometry) {
|
|
100
106
|
let mergedGeometry = this.createMergedGeometry(geometries)
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
107
|
+
if (!mesh) {
|
|
108
|
+
mesh = new THREE.Mesh(mergedGeometry, material)
|
|
109
|
+
// Add the merged mesh to the scene
|
|
110
|
+
this.scene.add(mesh)
|
|
111
|
+
} else {
|
|
112
|
+
mesh.geometry = mergedGeometry
|
|
113
|
+
if (material) mesh.material = material
|
|
114
|
+
}
|
|
104
115
|
} else {
|
|
105
116
|
let mergedGeometry = this.updateMergedGeometry(
|
|
106
117
|
geometries,
|