@eturnity/eturnity_3d 9.13.0-google3dTile.0 → 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.0",
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.
@@ -41,6 +27,15 @@ function applyDoubleSideToTileScene(scene) {
41
27
  */
42
28
  const TILES_FRUSTUM_ERROR_TARGET = 4
43
29
 
30
+ /**
31
+ * Draw after merged base / roofs (renderOrder 10) and edges (15) so transparent
32
+ * tiles blend with geometry already in the color buffer instead of hiding it.
33
+ */
34
+ const GOOGLE_TILES_RENDER_ORDER = 25
35
+
36
+ /** Extra meters padded on each side of roof AABB for tile loading frustum */
37
+ const ROOFS_BOUNDS_MARGIN_M = 5
38
+
44
39
  /** Same rhythm as ThreeDModelOverlay.animatePlaceholder (sin² on time) */
45
40
  function placeholderBounceScale(timeSeconds) {
46
41
  const baseRadius = 0.65
@@ -67,28 +62,30 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
67
62
  // Dedicated camera for tile loading and resolution so the user's camera does not affect which tiles load
68
63
  // Orthographic camera looking down (Z up), covering -50 to +50 meters in X and Y
69
64
  this._tilesCamera = new THREE.OrthographicCamera(
70
- -50,
71
- 50,
72
- 50,
73
- -50,
65
+ -10,
66
+ 10,
67
+ 10,
68
+ -10,
74
69
  0.1,
75
70
  10000
76
71
  )
77
72
  this._tilesCamera.position.set(0, 0, -100)
78
73
  this._tilesCamera.lookAt(0, 0, 0)
79
74
  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
75
+ /** @type {{ xMin: number, xMax: number, yMin: number, yMax: number, zMin?: number, zMax?: number } | null} mm — same shape as Vuex getRoofsBounds (three-d-module) */
76
+ this.roofsBound = null
77
+ /** Padded roof-bounds center (m), same as tile camera look-at XY — placeholder sits at (cx, cy, 0) */
78
+ this._placeholderCenterX = 0
79
+ this._placeholderCenterY = 0
80
+ this._rayDirDown = new THREE.Vector3(0, 0, 1)
81
+ this._defaultTilesCamZ = -100
82
+ this._defaultOrthoHalfExtentM = 10
87
83
  this.sphereMesh = null
88
84
  /** Transparent bouncing sphere at origin; removed once ground align settles */
89
85
  this._groundAlignPlaceholderMesh = null
90
86
  /** True when raycast tile bbox min.z is within ±10 after stick-to-ground */
91
- this._tilesGroundAligned = false
87
+ this.tilesFullyLoaded = false
88
+ this.scene = null
92
89
  }
93
90
  getOverlayMeshForRaycast() {
94
91
  return this.tilesData?.tiles.group
@@ -98,24 +95,95 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
98
95
  return true
99
96
  }
100
97
 
98
+ _roofsBoundsAreEqual(a, b) {
99
+ if (a === b) return true
100
+ if (!a || !b) return false
101
+ return (
102
+ a.xMin === b.xMin &&
103
+ a.xMax === b.xMax &&
104
+ a.yMin === b.yMin &&
105
+ a.yMax === b.yMax
106
+ )
107
+ }
108
+
101
109
  /**
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
110
+ * Store roof AABB (mm) and fit {@link #_tilesCamera} to it (ortho frustum in m).
111
+ * Bounds shape matches Vuex `getRoofsBounds` in `three-d-module.js`.
112
+ * Camera sits above the bounds center; left/right/top/bottom are set so the world XY
113
+ * footprint matches [xMin,xMax]×[yMin,yMax] in meters (same convention as ThreeCanvas orthographic).
114
+ *
115
+ * @param {{ xMin: number, xMax: number, yMin: number, yMax: number, zMin?: number, zMax?: number } | null | undefined} boundsMm
104
116
  */
105
- raycastAtOrigin() {
106
- const { tilesWrapper } = this.tilesData || {}
107
- if (!tilesWrapper) return null
108
-
109
- tilesWrapper.updateMatrixWorld(true)
117
+ updateRoofsBound(boundsMm) {
118
+ this._tilesGroundAligned = false
119
+ if (
120
+ !boundsMm ||
121
+ !Number.isFinite(boundsMm.xMin) ||
122
+ boundsMm.xMin === Infinity
123
+ ) {
124
+ this.roofsBound = null
125
+ const h = this._defaultOrthoHalfExtentM
126
+ const cam = this._tilesCamera
127
+ const z = this._defaultTilesCamZ
128
+ cam.position.set(0, 0, z)
129
+ cam.left = -h
130
+ cam.right = h
131
+ cam.top = h
132
+ cam.bottom = -h
133
+ cam.lookAt(0, 0, 0)
134
+ cam.updateProjectionMatrix()
135
+ cam.updateMatrixWorld(true)
136
+ this._placeholderCenterX = 0
137
+ this._placeholderCenterY = 0
138
+ this._syncPlaceholderWorldXY()
139
+ return
140
+ }
110
141
 
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]
142
+ this.roofsBound = {
143
+ xMin: boundsMm.xMin,
144
+ xMax: boundsMm.xMax,
145
+ yMin: boundsMm.yMin,
146
+ yMax: boundsMm.yMax,
147
+ zMin: boundsMm.zMin,
148
+ zMax: boundsMm.zMax,
117
149
  }
118
- return null
150
+
151
+ const m = ROOFS_BOUNDS_MARGIN_M
152
+ const xMinM = boundsMm.xMin / 1000 - m
153
+ const xMaxM = boundsMm.xMax / 1000 + m
154
+ const yMinM = boundsMm.yMin / 1000 - m
155
+ const yMaxM = boundsMm.yMax / 1000 + m
156
+
157
+ const cx = (xMinM + xMaxM) / 2
158
+ const cy = (yMinM + yMaxM) / 2
159
+
160
+ let halfW = (xMaxM - xMinM) / 2
161
+ let halfH = (yMaxM - yMinM) / 2
162
+ const minHalf = 100
163
+ if (halfW < minHalf) halfW = minHalf
164
+ if (halfH < minHalf) halfH = minHalf
165
+
166
+ const cam = this._tilesCamera
167
+ const z = this._defaultTilesCamZ
168
+ cam.position.set(cx, cy, z)
169
+ cam.left = xMinM - cx
170
+ cam.right = xMaxM - cx
171
+ cam.top = yMaxM - cy
172
+ cam.bottom = yMinM - cy
173
+ cam.lookAt(cx, cy, 0)
174
+ cam.updateProjectionMatrix()
175
+ cam.updateMatrixWorld(true)
176
+
177
+ this._placeholderCenterX = cx
178
+ this._placeholderCenterY = cy
179
+ this._syncPlaceholderWorldXY()
180
+ }
181
+
182
+ _syncPlaceholderWorldXY() {
183
+ const mesh = this._groundAlignPlaceholderMesh
184
+ if (!mesh) return
185
+ mesh.position.x = this._placeholderCenterX
186
+ mesh.position.y = this._placeholderCenterY
119
187
  }
120
188
 
121
189
  _getOrCreateTilesData() {
@@ -144,21 +212,28 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
144
212
  // Prefer loading tiles that intersect _tilesCamera before prefetch / out-of-frustum work
145
213
  tiles.optimizedLoadStrategy = true
146
214
 
147
- tiles.addEventListener('load-model', ({ scene }) => {
148
- applyDoubleSideToTileScene(scene)
149
- })
150
-
151
215
  tiles.lruCache.minSize = 400
152
216
  tiles.lruCache.maxSize = 800
153
217
  tiles.parseQueue.maxJobs = 4
154
218
 
155
219
  const tilesWrapper = new THREE.Group()
156
220
  tilesWrapper.name = 'Google3DTilesOverlay'
221
+ tilesWrapper.renderOrder = GOOGLE_TILES_RENDER_ORDER
157
222
  tilesWrapper.rotation.x = Math.PI / 2
158
223
  tilesWrapper.rotation.y = Math.PI
159
224
  tilesWrapper.add(tiles.group)
160
- tilesWrapper.position.z = -200
225
+ tilesWrapper.position.z = 0
161
226
  this.tilesData = { tiles, tilesWrapper }
227
+
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
+ }
235
+ })
236
+
162
237
  return this.tilesData
163
238
  }
164
239
 
@@ -183,7 +258,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
183
258
  const mesh = new THREE.Mesh(geometry, material)
184
259
  mesh.name = 'Google3DTilesGroundAlignPlaceholder'
185
260
  mesh.userData.googleTilesGroundAlignPlaceholder = true
186
- mesh.position.set(0, 0, 0)
261
+ mesh.position.set(this._placeholderCenterX, this._placeholderCenterY, 0)
187
262
  mesh.scale.setScalar(1)
188
263
  scene.add(mesh)
189
264
  this._groundAlignPlaceholderMesh = mesh
@@ -204,10 +279,10 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
204
279
  const t = performance.now() * 0.001
205
280
  const scale = placeholderBounceScale(t)
206
281
  mesh.scale.setScalar(scale)
282
+ mesh.position.x = this._placeholderCenterX
283
+ mesh.position.y = this._placeholderCenterY
207
284
  // Light Z bob so it reads as a bounce (Z up)
208
285
  mesh.position.z = 0.2 * Math.sin(t * 1.4 * Math.PI) ** 2
209
- mesh.position.x = 0
210
- mesh.position.y = 0
211
286
  mesh.material.opacity = 0.06 + 0.22 * Math.sin(t * 0.85 * Math.PI) ** 2
212
287
  }
213
288
 
@@ -227,52 +302,207 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
227
302
 
228
303
  _syncGroundAlignPlaceholder(threeJSComponent) {
229
304
  if (!threeJSComponent?.scene) return
230
- if (this._tilesGroundAligned) {
305
+ if (this.tilesFullyLoaded) {
231
306
  this._removeGroundAlignPlaceholder(threeJSComponent)
232
307
  } else {
233
308
  this._ensureGroundAlignPlaceholder(threeJSComponent)
234
309
  }
235
310
  }
236
-
237
- 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) {
238
325
  const { tiles, tilesWrapper } = this.tilesData || {}
239
326
  if (!tiles || !tilesWrapper) {
240
327
  this._tilesGroundAligned = false
241
328
  return
242
329
  }
243
330
 
244
- const raycastIntersect = this.raycastAtOrigin()
245
- if (!raycastIntersect) {
246
- this._tilesGroundAligned = false
247
- return
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
352
+ ) {
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)
248
364
  }
249
365
 
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()
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
379
+ }
257
380
 
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
381
+ /**
382
+ * Apply global overlay opacity to all tile mesh materials (base Overlay.setOpacity only updates state + emit).
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
+
421
+ _applyGoogleTilesOpacity(wrapper) {
422
+ if (!wrapper) return
423
+ const opacity = this.opacity
424
+ const isTransparent = opacity < 1 - 1e-6
425
+ wrapper.renderOrder = GOOGLE_TILES_RENDER_ORDER
426
+ wrapper.visible = Boolean(this.isActive) && opacity > 0
427
+ wrapper.traverse((object) => {
428
+ if (!object.isMesh || !object.material) return
429
+ object.renderOrder = GOOGLE_TILES_RENDER_ORDER
430
+ const materials = Array.isArray(object.material)
431
+ ? object.material
432
+ : [object.material]
433
+ for (const mat of materials) {
434
+ if (mat) {
435
+ mat.transparent = isTransparent
436
+ mat.opacity = opacity
437
+ // Avoid writing depth when semi-transparent so merged base (base.js) and
438
+ // other geometry drawn earlier remain visible through the tiles.
439
+ mat.depthWrite = !isTransparent
440
+ }
441
+ }
442
+ })
443
+ }
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
+ }
464
+ setOpacity(opacity) {
465
+ super.setOpacity(opacity)
466
+ const wrapper = this.overlayMesh || this.tilesData?.tilesWrapper
467
+ if (wrapper) {
468
+ this._applyGoogleTilesOpacity(wrapper)
266
469
  }
470
+ }
267
471
 
268
- tilesWrapper.updateMatrixWorld(true)
472
+ setIsActive(isActive) {
473
+ this.isActive = Boolean(isActive)
474
+ const wrapper = this.overlayMesh || this.tilesData?.tilesWrapper
475
+ if (wrapper) {
476
+ this._applyGoogleTilesOpacity(wrapper)
477
+ }
478
+ this.emit('item-updated', this)
269
479
  }
480
+
270
481
  async renderOnThreeJS(threeJSComponent) {
482
+ const rb = threeJSComponent?.roofsBounds
483
+ if (rb && !this._roofsBoundsAreEqual(this.roofsBound, rb)) {
484
+ this.updateRoofsBound(rb)
485
+ }
486
+
487
+ if (!this.isActive) {
488
+ this._removeGroundAlignPlaceholder(threeJSComponent)
489
+ const tilesWrapper =
490
+ this.tilesData?.tilesWrapper ||
491
+ threeJSComponent.meshes.overlayMeshes[this.id]
492
+ if (tilesWrapper) {
493
+ tilesWrapper.visible = false
494
+ threeJSComponent.meshes.overlayMeshes[this.id] = tilesWrapper
495
+ this.overlayMesh = tilesWrapper
496
+ }
497
+ return tilesWrapper || null
498
+ }
499
+
271
500
  const tilesData = this._getOrCreateTilesData()
272
501
  if (!tilesData) return null
273
502
 
274
503
  const { tiles, tilesWrapper } = tilesData
275
504
  const { renderer, scene } = threeJSComponent
505
+ this.scene = scene
276
506
  tiles.setCamera(this._tilesCamera)
277
507
  tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
278
508
 
@@ -280,21 +510,19 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
280
510
  tilesWrapper.userData.meshId = this.id
281
511
  tilesWrapper.userData.version = this.version
282
512
 
283
- this.stickObjectToTheGround()
284
513
  this._syncGroundAlignPlaceholder(threeJSComponent)
285
- if (this._groundAlignPlaceholderMesh) {
514
+ if (!this.tilesFullyLoaded) {
286
515
  this._tickGroundAlignPlaceholderAnimation()
287
516
  }
288
517
 
289
518
  tilesWrapper.userData.update = () => {
290
- if (!tiles || !renderer) return
519
+ if (!this.isActive || !tiles || !renderer) return
291
520
  tiles.errorTarget = TILES_FRUSTUM_ERROR_TARGET
292
521
  tiles.setCamera(this._tilesCamera)
293
522
  tiles.setResolutionFromRenderer(this._tilesCamera, renderer)
294
523
  tiles.update()
295
- this.stickObjectToTheGround()
296
524
  this._syncGroundAlignPlaceholder(threeJSComponent)
297
- if (this._groundAlignPlaceholderMesh) {
525
+ if (!this.tilesFullyLoaded) {
298
526
  this._tickGroundAlignPlaceholderAnimation()
299
527
  }
300
528
  }
@@ -306,6 +534,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
306
534
 
307
535
  threeJSComponent.meshes.overlayMeshes[this.id] = tilesWrapper
308
536
  this.overlayMesh = tilesWrapper
537
+ this._applyGoogleTilesOpacity(tilesWrapper)
309
538
  return tilesWrapper
310
539
  }
311
540
 
@@ -338,6 +567,7 @@ export default class Google3DTilesOverlay extends ThreeDModelOverlay {
338
567
  }
339
568
  this.tilesData = null
340
569
  this.overlayMesh = null
570
+ this.roofsBound = null
341
571
  this._initPromise = null
342
572
  this._tilesGroundAligned = false
343
573
  }