@eturnity/eturnity_3d 8.10.1 → 8.16.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 +2 -2
- package/src/Overlay/ImageOverlay.js +16 -139
- package/src/Overlay/Overlay.js +115 -14
- package/src/Overlay/OverlayFactory.js +3 -0
- package/src/Overlay/OverlayLayer.js +6 -0
- package/src/Overlay/ThreeDModelOverlay.js +16 -122
- package/src/Overlay/YearlySunShadingOverlay.js +368 -0
- package/src/helper/render/imageOverlay.js +23 -9
- package/src/helper/render/moduleField.js +12 -2
- package/src/helper/render/overlay.js +60 -5
- package/src/helper/render/projectionMaterial.js +2 -0
- package/src/helper/render/roof.js +0 -1
- package/src/helper/render/shadingMaterial/ShadingImageGenerator.js +241 -0
- package/src/helper/render/shadingMaterial/helper.js +109 -0
- package/src/helper/render/shadingMaterial/yearlySunShaderMaterial.js +389 -0
- package/src/helper/renderMixin.js +17 -0
- package/src/store/hydrateData.js +2 -0
- 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 +0 -43
- package/dist/assets/vue.svg +0 -1
- package/dist/main.es.js +0 -43662
- package/dist/main.umd.js +0 -3542
- package/dist/style.css +0 -1
- package/dist/vite.svg +0 -1
|
@@ -1,23 +1,14 @@
|
|
|
1
1
|
import Overlay from './Overlay'
|
|
2
2
|
import * as THREE from 'three'
|
|
3
|
-
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'
|
|
4
|
-
import { layerColors } from '../config'
|
|
5
3
|
import {
|
|
6
|
-
getDistanceBetweenPoints,
|
|
7
4
|
addVector,
|
|
8
5
|
multiplyVector,
|
|
9
|
-
substractVector,
|
|
10
|
-
getAngleInDegFromCanvasVector,
|
|
11
|
-
normalize2DVector,
|
|
12
6
|
isInsidePolygon,
|
|
13
7
|
calculateBestFittingPlanePolygon,
|
|
14
8
|
verticalProjectionOnPlane,
|
|
15
|
-
vectorLength2D,
|
|
16
|
-
deltaLatLngToDistance,
|
|
17
9
|
rotateTransformation,
|
|
18
10
|
getStandardDeviation,
|
|
19
11
|
} from '@eturnity/eturnity_maths'
|
|
20
|
-
let Paper
|
|
21
12
|
export default class ThreeDModelOverlay extends Overlay {
|
|
22
13
|
constructor(overlay, emit, origin, loader) {
|
|
23
14
|
super(overlay, emit)
|
|
@@ -44,15 +35,6 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
44
35
|
this.geoLocalisation = this.settings.geoLocalisation
|
|
45
36
|
}
|
|
46
37
|
loadFile() {}
|
|
47
|
-
onLoad() {
|
|
48
|
-
return new Promise((resolve) => {
|
|
49
|
-
if (this.isReady) {
|
|
50
|
-
resolve(this)
|
|
51
|
-
} else {
|
|
52
|
-
this.isReadyCallback.push(resolve)
|
|
53
|
-
}
|
|
54
|
-
})
|
|
55
|
-
}
|
|
56
38
|
set3DObjectMaterialOpacity(obj) {
|
|
57
39
|
obj.children.forEach((child) => {
|
|
58
40
|
this.set3DObjectMaterialOpacity(child, this.opacity)
|
|
@@ -65,7 +47,6 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
65
47
|
if (this.overlayMesh && this.overlayMesh.children.length == 0) {
|
|
66
48
|
this.overlayMesh = await this.loadFile()
|
|
67
49
|
}
|
|
68
|
-
|
|
69
50
|
if (!this.isReady) {
|
|
70
51
|
let waitingBall = threeJSComponent.meshes.overlayMeshes[this.id]
|
|
71
52
|
if (!waitingBall) {
|
|
@@ -126,9 +107,10 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
126
107
|
this.overlayMesh = overlayMesh
|
|
127
108
|
this.overlayMesh.frustumCulled = false
|
|
128
109
|
this.set3DObjectMaterialOpacity(this.overlayMesh)
|
|
110
|
+
return overlayMesh
|
|
129
111
|
}
|
|
130
112
|
renderOnPaperJS(paperJSComponent) {
|
|
131
|
-
Paper = paperJSComponent.paperScope
|
|
113
|
+
this.Paper = paperJSComponent.paperScope
|
|
132
114
|
if (!this.isActive) {
|
|
133
115
|
return
|
|
134
116
|
}
|
|
@@ -139,7 +121,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
139
121
|
if (!paperJSComponent.loadingSpinner) {
|
|
140
122
|
return
|
|
141
123
|
}
|
|
142
|
-
group = new Paper.Group()
|
|
124
|
+
group = new this.Paper.Group()
|
|
143
125
|
|
|
144
126
|
group.addChild(this.renderImagePlaceHolder(paperJSComponent))
|
|
145
127
|
group.itemType = 'placeholderOverlays'
|
|
@@ -150,7 +132,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
150
132
|
this.placeholderPath = group
|
|
151
133
|
|
|
152
134
|
paperJSComponent.paperLayer['loadingSpinners'].activate()
|
|
153
|
-
Paper.view.onFrame = (event) => this.animatePlaceholder(event)
|
|
135
|
+
this.Paper.view.onFrame = (event) => this.animatePlaceholder(event)
|
|
154
136
|
}
|
|
155
137
|
paperJSComponent.paths.imageOverlays[this.id] = group
|
|
156
138
|
this.hasPlaceholderPath = true
|
|
@@ -174,7 +156,9 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
174
156
|
let isRotationHandleVisible =
|
|
175
157
|
paperJSComponent.selectedTool == 'selectOverlay' && this.isSelected
|
|
176
158
|
|
|
177
|
-
|
|
159
|
+
const getCorners = () =>
|
|
160
|
+
this.corners.map((p) => paperJSComponent.toCanvasRef(p))
|
|
161
|
+
let corners = getCorners()
|
|
178
162
|
if (group) {
|
|
179
163
|
imageOverlayPath = group.children[0]
|
|
180
164
|
handles = this.renderImageOverlayHandles(
|
|
@@ -187,8 +171,8 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
187
171
|
group.opacity = this.opacity
|
|
188
172
|
group.transparent = this.opacity != 1
|
|
189
173
|
} else {
|
|
190
|
-
group = new Paper.Group()
|
|
191
|
-
imageOverlayPath = this.loadImageOverlay(this.imageUrl,
|
|
174
|
+
group = new this.Paper.Group()
|
|
175
|
+
imageOverlayPath = this.loadImageOverlay(this.imageUrl, getCorners)
|
|
192
176
|
this.applyRasterToCorners(imageOverlayPath, corners)
|
|
193
177
|
handles = this.renderImageOverlayHandles(corners, isRotationHandleVisible)
|
|
194
178
|
group.addChild(imageOverlayPath)
|
|
@@ -209,15 +193,15 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
209
193
|
renderImagePlaceHolder(paperJSComponent) {
|
|
210
194
|
const toCanvasRef = paperJSComponent.canvasContext.toCanvasRef
|
|
211
195
|
|
|
212
|
-
const group = new Paper.Group()
|
|
213
|
-
const circle = new Paper.Path.Circle({
|
|
196
|
+
const group = new this.Paper.Group()
|
|
197
|
+
const circle = new this.Paper.Path.Circle({
|
|
214
198
|
center: toCanvasRef({ x: 0, y: 0 }),
|
|
215
199
|
radius: 50,
|
|
216
200
|
fillColor: '#ffffff80',
|
|
217
201
|
})
|
|
218
202
|
circle.radius = 70
|
|
219
203
|
circle.itemData = 'circle'
|
|
220
|
-
const fillRectangle = new Paper.Path()
|
|
204
|
+
const fillRectangle = new this.Paper.Path()
|
|
221
205
|
fillRectangle.fillColor = '#ffffff80'
|
|
222
206
|
fillRectangle.itemType = 'placeholder'
|
|
223
207
|
fillRectangle.itemData = this
|
|
@@ -247,92 +231,16 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
247
231
|
circle.scale(scale)
|
|
248
232
|
circle.radius = newRadius
|
|
249
233
|
}
|
|
250
|
-
loadImageOverlay(url, corners) {
|
|
251
|
-
let rasterImageOverlay = new Paper.Raster({
|
|
252
|
-
source: url,
|
|
253
|
-
crossOrigin: 'Anonymous',
|
|
254
|
-
})
|
|
255
|
-
rasterImageOverlay.visible = false
|
|
256
|
-
rasterImageOverlay.onLoad = () => {
|
|
257
|
-
this.applyRasterToCorners(rasterImageOverlay, corners)
|
|
258
|
-
}
|
|
259
|
-
rasterImageOverlay.data.angle = 0
|
|
260
|
-
rasterImageOverlay.data.type = 'imageOverlayRaster'
|
|
261
|
-
rasterImageOverlay.data.overlayId = this.id
|
|
262
|
-
rasterImageOverlay.data.url = url
|
|
263
|
-
return rasterImageOverlay
|
|
264
|
-
}
|
|
265
|
-
applyRasterToCorners(texture, corners = this.corners) {
|
|
266
|
-
//remove angle of texture
|
|
267
|
-
let textureAngle = texture.angle || 0
|
|
268
|
-
texture.rotate(-textureAngle)
|
|
269
|
-
|
|
270
|
-
//make texture the same size to fit corners
|
|
271
|
-
const textureWidth = texture.bounds.width
|
|
272
|
-
const textureHeight = texture.bounds.height
|
|
273
|
-
const frameWidth = getDistanceBetweenPoints(corners[1], corners[0])
|
|
274
|
-
const frameHeight = getDistanceBetweenPoints(corners[3], corners[0])
|
|
275
|
-
const center = multiplyVector(0.5, addVector(corners[0], corners[2]))
|
|
276
|
-
if (texture.bounds.width) {
|
|
277
|
-
var a = frameWidth / textureWidth
|
|
278
|
-
var b = 0
|
|
279
|
-
var c = 0
|
|
280
|
-
var d = frameHeight / textureHeight
|
|
281
|
-
texture.transform(new Paper.Matrix(a, b, c, d, 0, 0))
|
|
282
|
-
//translate to fit top left corner
|
|
283
|
-
var tx = center.x - texture.bounds.center.x
|
|
284
|
-
var ty = center.y - texture.bounds.center.y
|
|
285
|
-
texture.transform(new Paper.Matrix(1, 0, 0, 1, tx, ty))
|
|
286
|
-
//rotate
|
|
287
|
-
let angle = getAngleInDegFromCanvasVector(
|
|
288
|
-
substractVector(corners[0], corners[3])
|
|
289
|
-
)
|
|
290
|
-
|
|
291
|
-
texture.angle = angle
|
|
292
|
-
texture.rotate(angle)
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
renderImageOverlayHandles(corners, rotateHandleVisible, group = null) {
|
|
296
|
-
let circleHandle
|
|
297
|
-
let hasPaths = !!group
|
|
298
|
-
let handles = group || new Paper.Group()
|
|
299
|
-
let u = normalize2DVector(substractVector(corners[1], corners[0]))
|
|
300
|
-
let v = normalize2DVector(substractVector(corners[0], corners[3]))
|
|
301
|
-
|
|
302
|
-
//rotation handle
|
|
303
|
-
let center = multiplyVector(0.5, addVector(corners[0], corners[2]))
|
|
304
|
-
|
|
305
|
-
let radius = vectorLength2D(substractVector(center, corners[0])) - 10
|
|
306
|
-
if (!hasPaths) {
|
|
307
|
-
circleHandle = new Paper.Path.Circle(center, radius)
|
|
308
|
-
circleHandle.fillColor = layerColors.imageOverlayHandle.fillColor
|
|
309
|
-
circleHandle.strokeWidth = layerColors.imageOverlayHandle.strokeWidth
|
|
310
|
-
circleHandle.strokeColor = layerColors.imageOverlayHandle.strokeColor
|
|
311
|
-
circleHandle.data.type = 'imageOverlayRotateHandle'
|
|
312
|
-
circleHandle.data.overlayId = this.id
|
|
313
|
-
circleHandle.radius = radius
|
|
314
|
-
handles.addChild(circleHandle)
|
|
315
|
-
} else {
|
|
316
|
-
circleHandle = handles.children[0]
|
|
317
|
-
let ratio = radius / circleHandle.radius
|
|
318
|
-
circleHandle.transform(new Paper.Matrix(ratio, 0, 0, ratio, 0, 0))
|
|
319
|
-
circleHandle.position = center
|
|
320
|
-
circleHandle.radius = radius
|
|
321
|
-
}
|
|
322
|
-
|
|
323
|
-
circleHandle.visible = rotateHandleVisible
|
|
324
|
-
return handles
|
|
325
|
-
}
|
|
326
234
|
initialiseCornersAndProjectionCamera() {
|
|
327
235
|
const box = new THREE.Box3().setFromObject(this.overlayMesh)
|
|
328
236
|
const boxSize = box.getSize(new THREE.Vector3())
|
|
329
|
-
this.boxSize = boxSize
|
|
330
237
|
const boxCenter = box.getCenter(new THREE.Vector3())
|
|
238
|
+
this.boxSize = boxSize
|
|
239
|
+
this.boxCenter = boxCenter
|
|
331
240
|
const left = -boxSize.x / 2
|
|
332
241
|
const right = +boxSize.x / 2
|
|
333
242
|
const top = +boxSize.y / 2
|
|
334
243
|
const bottom = -boxSize.y / 2
|
|
335
|
-
this.boxCenter = boxCenter
|
|
336
244
|
const initialCorners = [
|
|
337
245
|
{
|
|
338
246
|
x: 1000 * (boxCenter.x + left),
|
|
@@ -374,9 +282,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
374
282
|
1000 // far
|
|
375
283
|
)
|
|
376
284
|
}
|
|
377
|
-
checkGeoLocDataAndOffset() {
|
|
378
|
-
return
|
|
379
|
-
}
|
|
285
|
+
checkGeoLocDataAndOffset() {}
|
|
380
286
|
async initialiseModel() {
|
|
381
287
|
this.overlayMesh = await this.loadFile()
|
|
382
288
|
if (!this.geoLocalisation) {
|
|
@@ -442,7 +348,7 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
442
348
|
const imageUrl = canvas.toDataURL()
|
|
443
349
|
scene.remove(light)
|
|
444
350
|
scene.remove(ambientLight)
|
|
445
|
-
canvas.parentNode
|
|
351
|
+
canvas.parentNode?.removeChild(canvas)
|
|
446
352
|
resolve(imageUrl)
|
|
447
353
|
})
|
|
448
354
|
}
|
|
@@ -526,18 +432,6 @@ export default class ThreeDModelOverlay extends Overlay {
|
|
|
526
432
|
})
|
|
527
433
|
return newOutlines
|
|
528
434
|
}
|
|
529
|
-
createCanvas() {
|
|
530
|
-
const canvas = document.createElement('canvas')
|
|
531
|
-
canvas.width = this.canvasWidth
|
|
532
|
-
canvas.height = this.canvasHeight
|
|
533
|
-
canvas.style.position = 'fixed'
|
|
534
|
-
canvas.style.top = '100px'
|
|
535
|
-
canvas.style.left = '50px'
|
|
536
|
-
canvas.style.zIndex = 0
|
|
537
|
-
// Add the canvas to the document body
|
|
538
|
-
document.body.appendChild(canvas)
|
|
539
|
-
return canvas
|
|
540
|
-
}
|
|
541
435
|
serializeSettings() {
|
|
542
436
|
return {
|
|
543
437
|
version: this.version,
|
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import * as THREE from 'three'
|
|
2
|
+
import Overlay from './Overlay.js'
|
|
3
|
+
import {
|
|
4
|
+
getEdgesForHorizonForShader,
|
|
5
|
+
blurImagePreserveTransparency,
|
|
6
|
+
} from '../helper/render/shadingMaterial/helper.js'
|
|
7
|
+
import yearlySunShaderMaterial from '../helper/render/shadingMaterial/yearlySunShaderMaterial.js'
|
|
8
|
+
import {
|
|
9
|
+
updateBufferRoofGeometry,
|
|
10
|
+
getBufferRoofGeometry,
|
|
11
|
+
} from '../helper/render/geometryHandler.js'
|
|
12
|
+
import {
|
|
13
|
+
intersectOutlines,
|
|
14
|
+
verticalProjectionOnPlane,
|
|
15
|
+
} from '@eturnity/eturnity_maths'
|
|
16
|
+
|
|
17
|
+
export default class YearlySunShadingOverlay extends Overlay {
|
|
18
|
+
constructor(overlay, emit) {
|
|
19
|
+
super(overlay, emit)
|
|
20
|
+
if (!this.constructorValidation(overlay)) {
|
|
21
|
+
return
|
|
22
|
+
}
|
|
23
|
+
this.hasToBeSavedInBE = false
|
|
24
|
+
this.dataGrids = overlay.settings.dataGrids
|
|
25
|
+
this.edges = overlay.settings.edges || []
|
|
26
|
+
this.roofs = overlay.settings.roofs || []
|
|
27
|
+
this.obstacles = overlay.settings.obstacles || []
|
|
28
|
+
this.latitude = overlay.settings.lat
|
|
29
|
+
this.longitude = overlay.settings.lng
|
|
30
|
+
this.isActive = false
|
|
31
|
+
this.opacity = 1
|
|
32
|
+
this.projectionMaterialPriority = 40
|
|
33
|
+
this.colorMap = overlay.settings.colorMap
|
|
34
|
+
const hash = [...this.roofs, ...this.obstacles, ...this.edges].reduce(
|
|
35
|
+
(hash, item) => {
|
|
36
|
+
hash[item.id] = item.version
|
|
37
|
+
return hash
|
|
38
|
+
},
|
|
39
|
+
{}
|
|
40
|
+
)
|
|
41
|
+
// Update the stored hash
|
|
42
|
+
this.previousHash = hash
|
|
43
|
+
this.initialiseScene()
|
|
44
|
+
this.getImage().then((imageUrl) => {
|
|
45
|
+
this.imageUrl = imageUrl
|
|
46
|
+
this.isReady = true
|
|
47
|
+
this.version++
|
|
48
|
+
this.emit('overlay-is-ready', { id: this.id })
|
|
49
|
+
this.isReadyCallback.forEach((callback) => {
|
|
50
|
+
if (typeof callback === 'function') {
|
|
51
|
+
callback()
|
|
52
|
+
}
|
|
53
|
+
})
|
|
54
|
+
this.isReadyCallback = []
|
|
55
|
+
})
|
|
56
|
+
}
|
|
57
|
+
constructorValidation(overlay) {
|
|
58
|
+
if (!overlay.settings.dataGrids) {
|
|
59
|
+
console.error('No dataGrids provided')
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
if (!overlay.settings.lat || !overlay.settings.lng) {
|
|
63
|
+
console.error('No lat long provided')
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
return true
|
|
67
|
+
}
|
|
68
|
+
initialiseScene() {
|
|
69
|
+
this.canvasWidth = 600
|
|
70
|
+
this.canvasHeight = 600
|
|
71
|
+
this.canvas = this.createCanvas()
|
|
72
|
+
this.scene = new THREE.Scene()
|
|
73
|
+
this.renderer = new THREE.WebGLRenderer({
|
|
74
|
+
canvas: this.canvas,
|
|
75
|
+
alpha: true,
|
|
76
|
+
})
|
|
77
|
+
this.renderer.setSize(this.canvasWidth, this.canvasHeight)
|
|
78
|
+
this.renderer.setClearColor(0x000000, 0) // Make background transparent
|
|
79
|
+
this.meshes = { roofMeshes: {}, obstacleMeshes: {} }
|
|
80
|
+
this.renderRoofs()
|
|
81
|
+
this.renderObstacles()
|
|
82
|
+
this.updateBox()
|
|
83
|
+
this.updateCorners()
|
|
84
|
+
this.updateCanvasFromBoxSize()
|
|
85
|
+
this.updateProjectionCamera()
|
|
86
|
+
}
|
|
87
|
+
updateCanvasFromBoxSize() {
|
|
88
|
+
if (!this.boxSize) return
|
|
89
|
+
this.canvasWidth = this.boxSize.x * 100 //(cm resolution)
|
|
90
|
+
this.canvasHeight = this.boxSize.y * 100
|
|
91
|
+
const maxSize = Math.max(this.canvasWidth, this.canvasHeight)
|
|
92
|
+
const CANVAS_MAX_SIZE = 2048
|
|
93
|
+
if (maxSize > CANVAS_MAX_SIZE) {
|
|
94
|
+
this.canvasWidth *= CANVAS_MAX_SIZE / maxSize
|
|
95
|
+
this.canvasHeight *= CANVAS_MAX_SIZE / maxSize
|
|
96
|
+
}
|
|
97
|
+
this.renderer.setSize(this.canvasWidth, this.canvasHeight)
|
|
98
|
+
this.canvas.width = this.canvasWidth
|
|
99
|
+
this.canvas.height = this.canvasHeight
|
|
100
|
+
}
|
|
101
|
+
renderRoofs() {
|
|
102
|
+
const roofsIds = this.roofs.map((r) => r.id)
|
|
103
|
+
Object.values(this.meshes.roofMeshes).forEach((mesh) => {
|
|
104
|
+
if (!roofsIds.includes(mesh.userData.id)) {
|
|
105
|
+
this.scene.remove(mesh)
|
|
106
|
+
delete this.meshes.roofMeshes[mesh.userData.id]
|
|
107
|
+
}
|
|
108
|
+
})
|
|
109
|
+
this.roofs.forEach((roof) => {
|
|
110
|
+
const normalVector = roof.normalVector
|
|
111
|
+
const point = roof.flatOutline[0]
|
|
112
|
+
const plane = { normalVector, point }
|
|
113
|
+
const edgesForHorizon = getEdgesForHorizonForShader(plane, this.edges)
|
|
114
|
+
let mesh = this.meshes.roofMeshes[roof.id]
|
|
115
|
+
let roofOutline = roof.outline
|
|
116
|
+
let cropedHolesOutlines = roof.holes.map((h) => {
|
|
117
|
+
//let's crop obstacle with roof outline
|
|
118
|
+
const cropedHoleOutline =
|
|
119
|
+
intersectOutlines(h.outline, roof.outline)[0] || []
|
|
120
|
+
const outline = cropedHoleOutline.map((p) => {
|
|
121
|
+
return {
|
|
122
|
+
x: p.x,
|
|
123
|
+
y: p.y,
|
|
124
|
+
z: verticalProjectionOnPlane(
|
|
125
|
+
p,
|
|
126
|
+
roof.normalVector,
|
|
127
|
+
roof.flatOutline[0]
|
|
128
|
+
).z,
|
|
129
|
+
}
|
|
130
|
+
})
|
|
131
|
+
return outline
|
|
132
|
+
})
|
|
133
|
+
cropedHolesOutlines = cropedHolesOutlines.filter((outline) => !!outline)
|
|
134
|
+
if (mesh) {
|
|
135
|
+
let geometry = mesh.geometry
|
|
136
|
+
geometry = updateBufferRoofGeometry(
|
|
137
|
+
roofOutline,
|
|
138
|
+
cropedHolesOutlines,
|
|
139
|
+
geometry
|
|
140
|
+
)
|
|
141
|
+
mesh.material.updateEdges(edgesForHorizon)
|
|
142
|
+
mesh.material.needsUpdate = true
|
|
143
|
+
mesh.geometry = geometry
|
|
144
|
+
mesh.geometry.needsUpdate = true
|
|
145
|
+
mesh.geometry.computeBoundingBox()
|
|
146
|
+
mesh.needsUpdate = true
|
|
147
|
+
} else {
|
|
148
|
+
let material = new yearlySunShaderMaterial({
|
|
149
|
+
dataGrids: this.dataGrids,
|
|
150
|
+
lat: this.latitude,
|
|
151
|
+
lng: this.longitude,
|
|
152
|
+
edges: edgesForHorizon,
|
|
153
|
+
isShadingFactor: true,
|
|
154
|
+
colorMap: this.colorMap,
|
|
155
|
+
})
|
|
156
|
+
let geometry = getBufferRoofGeometry(roof.outline, cropedHolesOutlines)
|
|
157
|
+
mesh = new THREE.Mesh(geometry, material)
|
|
158
|
+
mesh.material.updateEdges(edgesForHorizon)
|
|
159
|
+
mesh.userData.id = roof.id
|
|
160
|
+
mesh.material.needsUpdate = true
|
|
161
|
+
mesh.needsUpdate = true
|
|
162
|
+
this.meshes.roofMeshes[roof.id] = mesh
|
|
163
|
+
this.scene.add(mesh)
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
}
|
|
167
|
+
renderObstacles() {
|
|
168
|
+
const obstacleIds = this.obstacles.map((o) => o.id)
|
|
169
|
+
Object.values(this.meshes.obstacleMeshes).forEach((mesh) => {
|
|
170
|
+
if (!obstacleIds.includes(mesh.userData.id)) {
|
|
171
|
+
this.scene.remove(mesh)
|
|
172
|
+
delete this.meshes.obstacleMeshes[mesh.userData.id]
|
|
173
|
+
}
|
|
174
|
+
})
|
|
175
|
+
this.obstacles.forEach((obstacle) => {
|
|
176
|
+
let mesh = this.meshes.obstacleMeshes[obstacle.id]
|
|
177
|
+
let obstacleOutline = obstacle.outline
|
|
178
|
+
if (mesh) {
|
|
179
|
+
let geometry = mesh.geometry
|
|
180
|
+
geometry = updateBufferRoofGeometry(obstacleOutline, [], geometry)
|
|
181
|
+
mesh.geometry = geometry
|
|
182
|
+
mesh.geometry.needsUpdate = true
|
|
183
|
+
mesh.geometry.computeBoundingBox()
|
|
184
|
+
mesh.needsUpdate = true
|
|
185
|
+
} else {
|
|
186
|
+
let material = new THREE.MeshBasicMaterial({
|
|
187
|
+
color: 0x000000,
|
|
188
|
+
})
|
|
189
|
+
let geometry = getBufferRoofGeometry(obstacle.outline, [])
|
|
190
|
+
mesh = new THREE.Mesh(geometry, material)
|
|
191
|
+
mesh.userData.id = obstacle.id
|
|
192
|
+
this.meshes.obstacleMeshes[obstacle.id] = mesh
|
|
193
|
+
this.scene.add(mesh)
|
|
194
|
+
}
|
|
195
|
+
})
|
|
196
|
+
}
|
|
197
|
+
updateMaterial(roofs, obstacles, edges) {
|
|
198
|
+
return new Promise((resolve, reject) => {
|
|
199
|
+
this.roofs = roofs
|
|
200
|
+
this.obstacles = obstacles
|
|
201
|
+
this.edges = edges
|
|
202
|
+
// Create a hash of roof and edge IDs and versions
|
|
203
|
+
const newHash = [...roofs, ...obstacles, ...edges].reduce(
|
|
204
|
+
(hash, item) => {
|
|
205
|
+
hash[item.id] = item.version
|
|
206
|
+
return hash
|
|
207
|
+
},
|
|
208
|
+
{}
|
|
209
|
+
)
|
|
210
|
+
|
|
211
|
+
// Compare with the previous hash
|
|
212
|
+
if (
|
|
213
|
+
this.previousHash &&
|
|
214
|
+
JSON.stringify(this.previousHash) === JSON.stringify(newHash)
|
|
215
|
+
) {
|
|
216
|
+
resolve(this.imageUrl)
|
|
217
|
+
return
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Update the stored hash
|
|
221
|
+
this.previousHash = newHash
|
|
222
|
+
this.renderRoofs()
|
|
223
|
+
this.renderObstacles()
|
|
224
|
+
this.updateBox()
|
|
225
|
+
this.updateCorners()
|
|
226
|
+
this.updateCanvasFromBoxSize()
|
|
227
|
+
this.updateProjectionCamera()
|
|
228
|
+
this.getImage().then((imageUrl) => {
|
|
229
|
+
this.imageUrl = imageUrl
|
|
230
|
+
this.version++
|
|
231
|
+
this.emit('overlay-is-ready', { id: this.id })
|
|
232
|
+
this.isReadyCallback.forEach((callback) => {
|
|
233
|
+
if (typeof callback === 'function') {
|
|
234
|
+
callback()
|
|
235
|
+
}
|
|
236
|
+
})
|
|
237
|
+
resolve(this.imageUrl)
|
|
238
|
+
})
|
|
239
|
+
})
|
|
240
|
+
}
|
|
241
|
+
getImage() {
|
|
242
|
+
return new Promise((resolve, reject) => {
|
|
243
|
+
// Set camera position and look at the center of the model
|
|
244
|
+
this.overlayProjectionCamera.position.set(
|
|
245
|
+
this.boxCenter.x,
|
|
246
|
+
this.boxCenter.y,
|
|
247
|
+
this.boxCenter.z + 100
|
|
248
|
+
)
|
|
249
|
+
this.overlayProjectionCamera.lookAt(this.boxCenter.x, this.boxCenter.y, 0)
|
|
250
|
+
this.overlayProjectionCamera.updateProjectionMatrix()
|
|
251
|
+
// Render the scene
|
|
252
|
+
this.renderer.render(this.scene, this.overlayProjectionCamera)
|
|
253
|
+
|
|
254
|
+
// Generate image from canvas
|
|
255
|
+
const imageUrl = this.canvas.toDataURL()
|
|
256
|
+
blurImagePreserveTransparency(imageUrl, 15)
|
|
257
|
+
.then((blurredImageUrl) => {
|
|
258
|
+
resolve(blurredImageUrl)
|
|
259
|
+
})
|
|
260
|
+
.catch((error) => {
|
|
261
|
+
resolve(imageUrl)
|
|
262
|
+
})
|
|
263
|
+
})
|
|
264
|
+
}
|
|
265
|
+
updateBox() {
|
|
266
|
+
const box = new THREE.Box3().setFromObject(this.scene)
|
|
267
|
+
const boxSize = box.getSize(new THREE.Vector3())
|
|
268
|
+
this.boxSize = boxSize
|
|
269
|
+
this.boxCenter = box.getCenter(new THREE.Vector3())
|
|
270
|
+
}
|
|
271
|
+
updateProjectionCamera() {
|
|
272
|
+
const left = -this.boxSize.x / 2
|
|
273
|
+
const right = +this.boxSize.x / 2
|
|
274
|
+
const top = +this.boxSize.y / 2
|
|
275
|
+
const bottom = -this.boxSize.y / 2
|
|
276
|
+
if (!this.overlayProjectionCamera) {
|
|
277
|
+
this.initialiseProjectionCamera()
|
|
278
|
+
}
|
|
279
|
+
this.overlayProjectionCamera.position.set(
|
|
280
|
+
this.boxCenter.x,
|
|
281
|
+
this.boxCenter.y,
|
|
282
|
+
this.boxCenter.z + 100
|
|
283
|
+
)
|
|
284
|
+
this.overlayProjectionCamera.lookAt(this.boxCenter.x, this.boxCenter.y, 0)
|
|
285
|
+
this.overlayProjectionCamera.left = left
|
|
286
|
+
this.overlayProjectionCamera.right = right
|
|
287
|
+
this.overlayProjectionCamera.top = top
|
|
288
|
+
this.overlayProjectionCamera.bottom = bottom
|
|
289
|
+
this.overlayProjectionCamera.updateProjectionMatrix()
|
|
290
|
+
}
|
|
291
|
+
initialiseProjectionCamera() {
|
|
292
|
+
this.overlayProjectionCamera = new THREE.OrthographicCamera(
|
|
293
|
+
-1,
|
|
294
|
+
1,
|
|
295
|
+
1,
|
|
296
|
+
-1,
|
|
297
|
+
0.1, // near
|
|
298
|
+
1000 // far
|
|
299
|
+
)
|
|
300
|
+
this.overlayProjectionCamera.lookAt(this.boxCenter.x, this.boxCenter.y, 0)
|
|
301
|
+
this.overlayProjectionCamera.updateProjectionMatrix()
|
|
302
|
+
}
|
|
303
|
+
updateCorners() {
|
|
304
|
+
const left = -this.boxSize.x / 2
|
|
305
|
+
const right = +this.boxSize.x / 2
|
|
306
|
+
const top = +this.boxSize.y / 2
|
|
307
|
+
const bottom = -this.boxSize.y / 2
|
|
308
|
+
this.corners = [
|
|
309
|
+
{
|
|
310
|
+
x: 1000 * (this.boxCenter.x + left),
|
|
311
|
+
y: 1000 * (this.boxCenter.y + top),
|
|
312
|
+
z: 0,
|
|
313
|
+
},
|
|
314
|
+
{
|
|
315
|
+
x: 1000 * (this.boxCenter.x + right),
|
|
316
|
+
y: 1000 * (this.boxCenter.y + top),
|
|
317
|
+
z: 0,
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
x: 1000 * (this.boxCenter.x + right),
|
|
321
|
+
y: 1000 * (this.boxCenter.y + bottom),
|
|
322
|
+
z: 0,
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
x: 1000 * (this.boxCenter.x + left),
|
|
326
|
+
y: 1000 * (this.boxCenter.y + bottom),
|
|
327
|
+
z: 0,
|
|
328
|
+
},
|
|
329
|
+
]
|
|
330
|
+
this.updateCanvasFromBoxSize()
|
|
331
|
+
}
|
|
332
|
+
renderOnThreeJS(threeJSComponent) {}
|
|
333
|
+
renderOnPaperJS(paperJSComponent) {
|
|
334
|
+
this.Paper = paperJSComponent.paperScope
|
|
335
|
+
if (!this.isReady || !this.isActive || !this.corners) {
|
|
336
|
+
return
|
|
337
|
+
}
|
|
338
|
+
let group = paperJSComponent.paths.imageOverlays[this.id]
|
|
339
|
+
paperJSComponent.paperLayer['imageOverlays'].activate()
|
|
340
|
+
|
|
341
|
+
let imageOverlayPath
|
|
342
|
+
|
|
343
|
+
const getCorners = () =>
|
|
344
|
+
this.corners.map((p) => paperJSComponent.toCanvasRef(p))
|
|
345
|
+
const corners = getCorners()
|
|
346
|
+
imageOverlayPath = this.loadImageOverlay(this.imageUrl, getCorners)
|
|
347
|
+
this.applyRasterToCorners(imageOverlayPath, corners)
|
|
348
|
+
imageOverlayPath.data.type = 'heatmapShadowRaster'
|
|
349
|
+
imageOverlayPath.visible = true
|
|
350
|
+
if (group) {
|
|
351
|
+
group.removeChildren()
|
|
352
|
+
group.insertChild(0, imageOverlayPath)
|
|
353
|
+
group.version++
|
|
354
|
+
group.visible = true
|
|
355
|
+
} else {
|
|
356
|
+
group = new this.Paper.Group()
|
|
357
|
+
group.addChild(imageOverlayPath)
|
|
358
|
+
group.itemType = 'imageOverlays'
|
|
359
|
+
group.data.url = this.imageUrl
|
|
360
|
+
group.type = 'imageOverlays'
|
|
361
|
+
group.pathId = this.id
|
|
362
|
+
group.overlayId = this.id
|
|
363
|
+
group.visible = true
|
|
364
|
+
paperJSComponent.paths.imageOverlays[this.id] = group
|
|
365
|
+
}
|
|
366
|
+
imageOverlayPath.opacity = this.opacity
|
|
367
|
+
}
|
|
368
|
+
}
|
|
@@ -21,14 +21,23 @@ export default {
|
|
|
21
21
|
methods: {
|
|
22
22
|
renderImageOverlayOnBackground() {
|
|
23
23
|
const overlayGeometries = OverlayLayer.getReadyItems()
|
|
24
|
-
.filter(
|
|
24
|
+
.filter(
|
|
25
|
+
(overlay) =>
|
|
26
|
+
overlay.isReady &&
|
|
27
|
+
overlay.isActive &&
|
|
28
|
+
overlay.corners &&
|
|
29
|
+
overlay.type != 'yearly_sun_shading'
|
|
30
|
+
)
|
|
25
31
|
.map((overlay) => {
|
|
26
32
|
return overlay.getBackgroundGeometry()
|
|
27
33
|
})
|
|
34
|
+
const overlayMaterialsToBackground = this.overlayMaterials.filter((m) => {
|
|
35
|
+
return m.userData.overlayType != 'yearly_sun_shading'
|
|
36
|
+
})
|
|
28
37
|
const newBackgroundMesh = this.updateOrCreateMergedMesh(
|
|
29
38
|
this.meshes.backgroundMesh,
|
|
30
39
|
overlayGeometries,
|
|
31
|
-
|
|
40
|
+
overlayMaterialsToBackground
|
|
32
41
|
)
|
|
33
42
|
if (!this.meshes.backgroundMesh) {
|
|
34
43
|
this.meshes.backgroundMesh = newBackgroundMesh
|
|
@@ -37,20 +46,25 @@ export default {
|
|
|
37
46
|
this.meshes.backgroundMesh = newBackgroundMesh
|
|
38
47
|
}
|
|
39
48
|
this.meshes.backgroundMesh.position.z = 0.15
|
|
40
|
-
this.applyTextureOnMesh(
|
|
49
|
+
this.applyTextureOnMesh(
|
|
50
|
+
overlayMaterialsToBackground,
|
|
51
|
+
this.meshes.backgroundMesh
|
|
52
|
+
)
|
|
41
53
|
|
|
42
54
|
this.meshes.backgroundMesh.visible = true
|
|
43
55
|
return
|
|
44
56
|
},
|
|
45
57
|
async getOverlaysMaterials() {
|
|
46
58
|
let overlayMaterialsPromises = []
|
|
47
|
-
OverlayLayer.getReadyItems()
|
|
48
|
-
|
|
49
|
-
|
|
59
|
+
OverlayLayer.getReadyItems()
|
|
60
|
+
.filter((o) => o.isActive)
|
|
61
|
+
.forEach((overlay) => {
|
|
62
|
+
overlayMaterialsPromises.push(overlay.getProjectionMaterial())
|
|
63
|
+
})
|
|
50
64
|
overlayMaterialsPromises = overlayMaterialsPromises.filter((p) => !!p)
|
|
51
|
-
const
|
|
52
|
-
this.
|
|
53
|
-
return this.
|
|
65
|
+
const imageOverlayMaterials = await Promise.all(overlayMaterialsPromises)
|
|
66
|
+
this.imageOverlayMaterials = imageOverlayMaterials.filter((m) => !!m)
|
|
67
|
+
return this.imageOverlayMaterials
|
|
54
68
|
},
|
|
55
69
|
},
|
|
56
70
|
}
|