@eturnity/eturnity_maths 1.0.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/babel.config.js +3 -0
- package/package.json +30 -0
- package/src/assets/theme.js +45 -0
- package/src/config.js +77 -0
- package/src/coords.js +24 -0
- package/src/geo.js +51 -0
- package/src/geometry.js +760 -0
- package/src/index.js +10 -0
- package/src/intersectionPolygon.js +623 -0
- package/src/matrix.js +53 -0
- package/src/objects/Circle.js +51 -0
- package/src/objects/Line.js +110 -0
- package/src/objects/Point.js +57 -0
- package/src/objects/Polygon.js +240 -0
- package/src/objects/derivedState/AddMargin.js +109 -0
- package/src/objects/derivedState/UpdateRoofModuleFieldRelations.js +119 -0
- package/src/objects/derivedState/UpdateRoofObstacleRelations.js +86 -0
- package/src/objects/derivedState/index.js +2 -0
- package/src/objects/derivedState/updateComputedGeometryPolygon.js +168 -0
- package/src/objects/graph/DFS.js +80 -0
- package/src/objects/graph/graphCreation.js +44 -0
- package/src/objects/hydrate.js +24 -0
- package/src/objects/index.js +8 -0
- package/src/splitMergePolygons.js +553 -0
- package/src/test/maths.test.js +10 -0
- package/src/vector.js +56 -0
- package/webpack.config.js +11 -0
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
getDistanceBetweenPoints,
|
|
4
|
+
translate2D,
|
|
5
|
+
} from '../geometry'
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
7
|
+
import {Point} from './Point'
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
export class Circle {
|
|
12
|
+
constructor(center, radius, layer) {
|
|
13
|
+
this.id = uuidv4()
|
|
14
|
+
this.type = 'circle'
|
|
15
|
+
this.version = 0
|
|
16
|
+
this.center = center
|
|
17
|
+
this.radius = radius
|
|
18
|
+
this.layer = layer
|
|
19
|
+
this.visible = true
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
clone() {
|
|
23
|
+
const circle = new Circle(
|
|
24
|
+
new Point(this.point.x, this.point.y),
|
|
25
|
+
this.radius,
|
|
26
|
+
this.layer
|
|
27
|
+
)
|
|
28
|
+
circle.id = uuidv4()
|
|
29
|
+
return circle
|
|
30
|
+
}
|
|
31
|
+
getPxDistanceTo(point, canvasContext) {
|
|
32
|
+
const toCanvasRef = canvasContext.toCanvasRef
|
|
33
|
+
let pxCenter = toCanvasRef(this.center)
|
|
34
|
+
let pxRadius = this.radius / canvasContext.mmPerPx
|
|
35
|
+
return Math.abs(getDistanceBetweenPoints(point, pxCenter) - pxRadius)
|
|
36
|
+
}
|
|
37
|
+
getProjectedPoint(point) {
|
|
38
|
+
let distance = getDistanceBetweenPoints(point, this.center)
|
|
39
|
+
if (distance == 0) {
|
|
40
|
+
console.error("can't project center to cercle", this)
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
let x = this.center.x + (point.x - this.center.x) * (this.radius / distance)
|
|
44
|
+
let y = this.center.y + (point.y - this.center.y) * (this.radius / distance)
|
|
45
|
+
let P = new Point(x, y, 0, this.layer)
|
|
46
|
+
return P
|
|
47
|
+
}
|
|
48
|
+
translate(vectorInMm) {
|
|
49
|
+
this.center = translate2D(this.center, vectorInMm)
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
getDistanceBetweenPoints,
|
|
4
|
+
getPointOnLine,
|
|
5
|
+
isSamePoint3D,
|
|
6
|
+
translate2D,
|
|
7
|
+
midPoint,
|
|
8
|
+
} from '../geometry'
|
|
9
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
10
|
+
import {Point} from './Point'
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
export class Line {
|
|
14
|
+
constructor(firstPoint, lastPoint, layer, infiniteLine = false) {
|
|
15
|
+
this.id = uuidv4()
|
|
16
|
+
this.version = 0
|
|
17
|
+
this.type = 'line'
|
|
18
|
+
if (firstPoint && lastPoint) {
|
|
19
|
+
this.outline = [firstPoint, lastPoint]
|
|
20
|
+
} else {
|
|
21
|
+
this.outline = []
|
|
22
|
+
}
|
|
23
|
+
this.layer = layer
|
|
24
|
+
this.visible = true
|
|
25
|
+
this.infiniteLine = infiniteLine
|
|
26
|
+
}
|
|
27
|
+
clone() {
|
|
28
|
+
const line = new Line()
|
|
29
|
+
this.outline.forEach((p) => {
|
|
30
|
+
line.outline.push(new Point(p.x, p.y, p.z))
|
|
31
|
+
})
|
|
32
|
+
line.id = uuidv4()
|
|
33
|
+
line.layer = this.layer
|
|
34
|
+
line.infiniteLine = this.infiniteLine
|
|
35
|
+
return line
|
|
36
|
+
}
|
|
37
|
+
pxLength(canvasContext) {
|
|
38
|
+
return (
|
|
39
|
+
getDistanceBetweenPoints(this.outline[0], this.outline[1]) /
|
|
40
|
+
canvasContext.mmPerPx
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
mmLength(canvasContext) {
|
|
44
|
+
return getDistanceBetweenPoints(this.outline[0], this.outline[1])
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
getMidPoint() {
|
|
48
|
+
return midPoint(this.outline[0], this.outline[1])
|
|
49
|
+
}
|
|
50
|
+
getAngleDeg(){
|
|
51
|
+
const angle = Math.atan2(
|
|
52
|
+
this.outline[1].y - this.outline[0].y,
|
|
53
|
+
this.outline[0].x - this.outline[1].x
|
|
54
|
+
)
|
|
55
|
+
return (angle * 180) / Math.PI
|
|
56
|
+
}
|
|
57
|
+
getPxDistanceTo(point, canvasContext) {
|
|
58
|
+
const toCanvasRef = canvasContext.toCanvasRef
|
|
59
|
+
const toRealityRef = canvasContext.toRealityRef
|
|
60
|
+
|
|
61
|
+
if (this.outline.length < 2) {
|
|
62
|
+
console.error('line undefined')
|
|
63
|
+
return null
|
|
64
|
+
}
|
|
65
|
+
if (isSamePoint3D(this.outline[0], this.outline[1])) {
|
|
66
|
+
console.error('line undefined')
|
|
67
|
+
return null
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
let realityRefPoint = toRealityRef(point)
|
|
71
|
+
let P = getPointOnLine(realityRefPoint, this.outline[0], this.outline[1])
|
|
72
|
+
return getDistanceBetweenPoints(realityRefPoint, P) / canvasContext.mmPerPx
|
|
73
|
+
}
|
|
74
|
+
getProjectedPoint(point) {
|
|
75
|
+
if (this.outline.length == 0) {
|
|
76
|
+
return null
|
|
77
|
+
}
|
|
78
|
+
if (isSamePoint3D(this.outline[0], this.outline[1])) {
|
|
79
|
+
console.error("A == B Can't project point on Line")
|
|
80
|
+
return null
|
|
81
|
+
}
|
|
82
|
+
let P = getPointOnLine(point, this.outline[0], this.outline[1])
|
|
83
|
+
return P
|
|
84
|
+
}
|
|
85
|
+
translate(vectorInMm) {
|
|
86
|
+
this.outline = this.outline.map((point) => {
|
|
87
|
+
return translate2D(point, vectorInMm)
|
|
88
|
+
})
|
|
89
|
+
}
|
|
90
|
+
serialize() {
|
|
91
|
+
if (!this.belongsTo) {
|
|
92
|
+
this.belongsTo = []
|
|
93
|
+
}
|
|
94
|
+
const belongsTo = this.belongsTo.map((item) => {
|
|
95
|
+
return {
|
|
96
|
+
polygon: item.polygon.serialize(),
|
|
97
|
+
polygonId: item.polygonId,
|
|
98
|
+
index: item.index
|
|
99
|
+
}
|
|
100
|
+
})
|
|
101
|
+
const serializedEdge={
|
|
102
|
+
outline: [this.outline[0], this.outline[1]],
|
|
103
|
+
layer: this.layer,
|
|
104
|
+
type: this.type,
|
|
105
|
+
belongsTo: belongsTo || [],
|
|
106
|
+
itemType: this.itemType
|
|
107
|
+
}
|
|
108
|
+
return JSON.parse(JSON.stringify(serializedEdge))
|
|
109
|
+
}
|
|
110
|
+
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
getDistanceBetweenPoints,
|
|
4
|
+
translate2D,
|
|
5
|
+
} from '../geometry'
|
|
6
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
7
|
+
|
|
8
|
+
export class Point {
|
|
9
|
+
constructor(x = 0, y = 0, z = 0, layer = null) {
|
|
10
|
+
this.id = uuidv4()
|
|
11
|
+
this.type = 'point'
|
|
12
|
+
this.version = 0
|
|
13
|
+
this.x = x
|
|
14
|
+
this.y = y
|
|
15
|
+
this.z = z
|
|
16
|
+
this.layer = layer
|
|
17
|
+
this.open = false
|
|
18
|
+
this.selected = false
|
|
19
|
+
this.visible = true
|
|
20
|
+
this.hasWarning = false
|
|
21
|
+
}
|
|
22
|
+
getPxDistanceTo(point, canvasContext) {
|
|
23
|
+
const toRealityRef = canvasContext.toRealityRef
|
|
24
|
+
const realityPoint = toRealityRef(point)
|
|
25
|
+
return getDistanceBetweenPoints(realityPoint, this) / canvasContext.mmPerPx
|
|
26
|
+
}
|
|
27
|
+
getProjectedPoint(point) {
|
|
28
|
+
return this
|
|
29
|
+
}
|
|
30
|
+
translate(vectorInMm) {
|
|
31
|
+
let translatedPoint = translate2D({ x: this.x, y: this.y }, vectorInMm)
|
|
32
|
+
this.x = translatedPoint.x
|
|
33
|
+
this.y = translatedPoint.y
|
|
34
|
+
}
|
|
35
|
+
serialize() {
|
|
36
|
+
if (!this.belongsTo) {
|
|
37
|
+
this.belongsTo = []
|
|
38
|
+
}
|
|
39
|
+
const belongsTo = this.belongsTo.map((item) => {
|
|
40
|
+
return {
|
|
41
|
+
polygon: item.polygon.serialize(),
|
|
42
|
+
polygonId: item.polygonId,
|
|
43
|
+
index: item.index
|
|
44
|
+
}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
return {
|
|
48
|
+
x: this.x,
|
|
49
|
+
y: this.y,
|
|
50
|
+
z: this.z,
|
|
51
|
+
layer: this.layer,
|
|
52
|
+
type: this.type,
|
|
53
|
+
belongsTo: belongsTo || [],
|
|
54
|
+
itemType: this.itemType
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
|
|
2
|
+
import {
|
|
3
|
+
translate2D,
|
|
4
|
+
getDegree
|
|
5
|
+
} from '../geometry'
|
|
6
|
+
import {
|
|
7
|
+
substractVector,
|
|
8
|
+
normalizeVector,
|
|
9
|
+
multiplyVector,
|
|
10
|
+
addVector
|
|
11
|
+
} from '../vector'
|
|
12
|
+
|
|
13
|
+
import { v4 as uuidv4 } from 'uuid'
|
|
14
|
+
import {updateComputedGeometryPolygon} from './derivedState/updateComputedGeometryPolygon'
|
|
15
|
+
|
|
16
|
+
export class Polygon {
|
|
17
|
+
constructor(outline = [], layer, isClosed = true) {
|
|
18
|
+
this.id = uuidv4()
|
|
19
|
+
this.name = ''
|
|
20
|
+
this.version = 0
|
|
21
|
+
this.type = 'polygon'
|
|
22
|
+
this.outline = JSON.parse(JSON.stringify(outline))
|
|
23
|
+
this.holes = []
|
|
24
|
+
this.layer = layer
|
|
25
|
+
this.isClosed = isClosed
|
|
26
|
+
this.highlight = false
|
|
27
|
+
this.visible = true
|
|
28
|
+
this.heightReference = 0
|
|
29
|
+
this.margins = {
|
|
30
|
+
isSameMargin: true,
|
|
31
|
+
sameMargin: 200,
|
|
32
|
+
innerOutline: [],
|
|
33
|
+
outterOutline: [],
|
|
34
|
+
distances: []
|
|
35
|
+
}
|
|
36
|
+
this.isLoading = false
|
|
37
|
+
if (this.layer == 'obstacle') {
|
|
38
|
+
this.isParallel = true
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
clone() {
|
|
43
|
+
const polygon = new Polygon()
|
|
44
|
+
this.outline.forEach((p) => {
|
|
45
|
+
polygon.outline.push({ ...p })
|
|
46
|
+
})
|
|
47
|
+
this.holes.forEach((h) => {
|
|
48
|
+
polygon.holes.push({ ...h })
|
|
49
|
+
})
|
|
50
|
+
polygon.id = uuidv4()
|
|
51
|
+
polygon.layer = this.layer
|
|
52
|
+
polygon.isClosed = this.isClosed
|
|
53
|
+
return polygon
|
|
54
|
+
}
|
|
55
|
+
getAngle(i){
|
|
56
|
+
const length=this.outline.length
|
|
57
|
+
const firstPoint = this.outline[(i-1+length) % length]
|
|
58
|
+
const secondPoint = this.outline[i % length]
|
|
59
|
+
const thirdPoint = this.outline[(i + 1) % length]
|
|
60
|
+
return getDegree(firstPoint, secondPoint, thirdPoint)
|
|
61
|
+
}
|
|
62
|
+
getDirectionSnapAngle(){
|
|
63
|
+
let angles=[]
|
|
64
|
+
for(let k=0;k<this.outline.length;k++){
|
|
65
|
+
let p_0=this.outline[k]
|
|
66
|
+
let p_1=this.outline[(k+1)%this.outline.length]
|
|
67
|
+
let angle = Math.atan2(
|
|
68
|
+
p_0.x - p_1.x,
|
|
69
|
+
p_0.y - p_1.y
|
|
70
|
+
)
|
|
71
|
+
angle*=180/Math.PI
|
|
72
|
+
angles.push(angle+90%360)
|
|
73
|
+
angles.push(angle+270%360)
|
|
74
|
+
}
|
|
75
|
+
return angles
|
|
76
|
+
}
|
|
77
|
+
getBounds(){
|
|
78
|
+
let bounds=this.outline.reduce((acc,cur)=>{
|
|
79
|
+
return{
|
|
80
|
+
xMin:Math.min(acc.xMin,cur.x),
|
|
81
|
+
yMin:Math.min(acc.yMin,cur.y),
|
|
82
|
+
xMax:Math.max(acc.xMax,cur.x),
|
|
83
|
+
yMax:Math.max(acc.yMax,cur.y),
|
|
84
|
+
}
|
|
85
|
+
},{xMin:Infinity,xMax:-Infinity,yMin:Infinity,yMax:-Infinity})
|
|
86
|
+
let center={
|
|
87
|
+
x:(bounds.xMax+bounds.xMin)/2,
|
|
88
|
+
y:(bounds.yMax+bounds.yMin)/2,
|
|
89
|
+
}
|
|
90
|
+
let radius=Math.hypot(bounds.xMax-bounds.xMin,bounds.yMax-bounds.yMin)/2
|
|
91
|
+
return {...bounds,radius,center}
|
|
92
|
+
}
|
|
93
|
+
get4pointsSquareAngle(i,canvasContext){
|
|
94
|
+
const length=this.outline.length
|
|
95
|
+
const A = this.outline[(i-1+length) % length]
|
|
96
|
+
const B = this.outline[i % length]
|
|
97
|
+
const C = this.outline[(i + 1) % length]
|
|
98
|
+
const toCanvasRef = canvasContext.toCanvasRef
|
|
99
|
+
//angle in B
|
|
100
|
+
const BD=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(A,B)))
|
|
101
|
+
const BE=multiplyVector(15*canvasContext.mmPerPx,normalizeVector(substractVector(C,B)))
|
|
102
|
+
const D=addVector(B,BD)
|
|
103
|
+
const E=addVector(B,BE)
|
|
104
|
+
const F=addVector(D,BE)
|
|
105
|
+
|
|
106
|
+
return [toCanvasRef(D),toCanvasRef(F),toCanvasRef(E)]
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
translate(vectorInMm) {
|
|
110
|
+
this.version++
|
|
111
|
+
this.outline = this.outline.map((point) => {
|
|
112
|
+
return translate2D(point, vectorInMm)
|
|
113
|
+
})
|
|
114
|
+
updateComputedGeometryPolygon(this)
|
|
115
|
+
}
|
|
116
|
+
serialize() {
|
|
117
|
+
const baseSerialization = {
|
|
118
|
+
id: this.id,
|
|
119
|
+
version: this.version,
|
|
120
|
+
name: this.name,
|
|
121
|
+
type: this.type,
|
|
122
|
+
outline: this.outline.map(p=>{return {...p}}),
|
|
123
|
+
holes: this.holes.map((p) => p.id),
|
|
124
|
+
layer: this.layer,
|
|
125
|
+
highlight: this.highlight,
|
|
126
|
+
visible: this.visible,
|
|
127
|
+
margins: JSON.parse(JSON.stringify(this.margins))
|
|
128
|
+
}
|
|
129
|
+
const extraSerialization = {}
|
|
130
|
+
if (this.layer == 'roof') {
|
|
131
|
+
extraSerialization.moduleFields = this.moduleFields.map((m) => {
|
|
132
|
+
return {
|
|
133
|
+
id: m.id
|
|
134
|
+
}
|
|
135
|
+
})
|
|
136
|
+
} else if (this.layer == 'obstacle') {
|
|
137
|
+
extraSerialization.isParallel = this.isParallel
|
|
138
|
+
extraSerialization.height = this.height
|
|
139
|
+
extraSerialization.roofs = this.roofs.map((roof) => {
|
|
140
|
+
return { id: roof.id }
|
|
141
|
+
})
|
|
142
|
+
} else if (this.layer == 'moduleField') {
|
|
143
|
+
extraSerialization.data = {...this.data}
|
|
144
|
+
extraSerialization.roof = { id: this.roof.id }
|
|
145
|
+
extraSerialization.pvData = this.pvData?{...this.pvData}:null
|
|
146
|
+
extraSerialization.mountingData = this.mountingData?{...this.mountingData}:null
|
|
147
|
+
extraSerialization.panels = this.panels
|
|
148
|
+
? this.panels.map((p) => {
|
|
149
|
+
return { id: p.id, index: p.index, outline: p.outline }
|
|
150
|
+
})
|
|
151
|
+
: []
|
|
152
|
+
extraSerialization.userDeactivatedPanels = this.userDeactivatedPanels
|
|
153
|
+
? this.userDeactivatedPanels.map((p) => {
|
|
154
|
+
return { id: p.id, index: p.index, outline: p.outline }
|
|
155
|
+
})
|
|
156
|
+
: []
|
|
157
|
+
} else if (this.layer == 'panel') {
|
|
158
|
+
extraSerialization.index = this.index
|
|
159
|
+
extraSerialization.moduleField = { id: this.moduleField.id }
|
|
160
|
+
} else if (this.layer == 'user_deactivated_panel') {
|
|
161
|
+
extraSerialization.index = this.index
|
|
162
|
+
extraSerialization.moduleField = { id: this.moduleField.id }
|
|
163
|
+
}
|
|
164
|
+
return JSON.parse(JSON.stringify({ ...baseSerialization, ...extraSerialization }))
|
|
165
|
+
}
|
|
166
|
+
serializeForBEStorage() {
|
|
167
|
+
if (this.layer == 'roof') {
|
|
168
|
+
const margins_roof_mm = this.margins.isSameMargin
|
|
169
|
+
? Array(this.outline.length).fill(this.margins.sameMargin)
|
|
170
|
+
: this.margins.distances
|
|
171
|
+
return {
|
|
172
|
+
roof_uuid: this.id,
|
|
173
|
+
version: this.version,
|
|
174
|
+
roof_outline: this.outline.map((v) => [v.x, v.y, v.z]),
|
|
175
|
+
roof_margins_mm: margins_roof_mm,
|
|
176
|
+
roof_slope_degrees: this.incline,
|
|
177
|
+
roof_orientation_degrees: this.direction,
|
|
178
|
+
roof_name: this.name
|
|
179
|
+
}
|
|
180
|
+
} else if (this.layer == 'obstacle') {
|
|
181
|
+
const margins_obstacle_mm = this.margins.isSameMargin
|
|
182
|
+
? Array(this.outline.length).fill(this.margins.sameMargin)
|
|
183
|
+
: this.margins.distances
|
|
184
|
+
return {
|
|
185
|
+
obstacle_uuid: this.id,
|
|
186
|
+
version: this.version,
|
|
187
|
+
obstacle_outline: this.outline.map((v) => [v.x, v.y, v.z]),
|
|
188
|
+
obstacle_margins_mm: margins_obstacle_mm,
|
|
189
|
+
obstacle_height_above_ground_mm: this.height,
|
|
190
|
+
obstacle_slope_is_parallel_to_roof: this.isParallel
|
|
191
|
+
}
|
|
192
|
+
} else if (['moduleField', 'tmpModuleField'].includes(this.layer)) {
|
|
193
|
+
if (!this.panels) {
|
|
194
|
+
this.panels = []
|
|
195
|
+
}
|
|
196
|
+
if (!this.userDeactivatedPanels) {
|
|
197
|
+
this.userDeactivatedPanels = []
|
|
198
|
+
}
|
|
199
|
+
const modules = this.panels.map((p) => {
|
|
200
|
+
return {
|
|
201
|
+
index: p.index,
|
|
202
|
+
outline: p.outline.map((v) => [v.x, v.y, v.z])
|
|
203
|
+
}
|
|
204
|
+
})
|
|
205
|
+
const user_deactivated_modules = this.userDeactivatedPanels.map((p) => {
|
|
206
|
+
return {
|
|
207
|
+
index: p.index,
|
|
208
|
+
outline: p.outline.map((v) => [v.x, v.y, v.z])
|
|
209
|
+
}
|
|
210
|
+
})
|
|
211
|
+
return {
|
|
212
|
+
module_field_uuid: this.id,
|
|
213
|
+
roof_uuid: this.roof.id,
|
|
214
|
+
version: this.version,
|
|
215
|
+
module_field_outline: this.outline.map((v) => [v.x, v.y, v.z]),
|
|
216
|
+
col_spacing_mm: this.data.col_spacing_mm,
|
|
217
|
+
row_spacing_mm: this.data.row_spacing_mm,
|
|
218
|
+
offset_percent: this.data.offset_percent,
|
|
219
|
+
panel_orientation: this.data.panel_orientation,
|
|
220
|
+
panel_direction_degrees: this.data.panel_direction_degrees,
|
|
221
|
+
component_id_pv_module: this.data.component_id_pv_module,
|
|
222
|
+
component_id_mounting_system: this.data.component_id_mounting_system,
|
|
223
|
+
modules: modules,
|
|
224
|
+
module_tilt_degrees: this.data.module_tilt_degrees,
|
|
225
|
+
user_deactivated_modules: user_deactivated_modules,
|
|
226
|
+
base_line_index: this.data.base_line_index,
|
|
227
|
+
justify_percent: this.data.justify_percent
|
|
228
|
+
}
|
|
229
|
+
} else if (['panel', 'user_deactivated_panel'].includes(this.layer)) {
|
|
230
|
+
return {
|
|
231
|
+
id: this.id,
|
|
232
|
+
index: this.index,
|
|
233
|
+
outline: this.outline.map((v) => [v.x, v.y, v.z]),
|
|
234
|
+
moduleField: {
|
|
235
|
+
id: this.moduleField.id
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import {
|
|
2
|
+
multiplyVector
|
|
3
|
+
} from '../../vector'
|
|
4
|
+
import {getMarginPoint} from '../../geometry'
|
|
5
|
+
//update and calculate margins for all polygon
|
|
6
|
+
export function updateMarginsOutline(state){
|
|
7
|
+
state.polygons.forEach(polygon=>{
|
|
8
|
+
polygon=updateMarginOutlinePolygon(polygon)
|
|
9
|
+
})
|
|
10
|
+
return state
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function updateMarginOutlinePolygon(polygon){
|
|
14
|
+
if(polygon.layer=="roof"){
|
|
15
|
+
return getRoofMarginOutline(polygon)
|
|
16
|
+
}else if(polygon.layer=='obstacle'){
|
|
17
|
+
return getObstacleMarginOutline(polygon)
|
|
18
|
+
}else{
|
|
19
|
+
return polygon
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
//FYI: polygon.margins={
|
|
23
|
+
// distances:array[#point] //primitives
|
|
24
|
+
// isSameMargin:Boolean //primitives
|
|
25
|
+
// sameMargin:Number //primitives
|
|
26
|
+
// outterOutline:array[Vector3D] //derived (for roof this is the wall)
|
|
27
|
+
// innerOutline:array[Vector3D] //derived (for obstacle this is the wall)
|
|
28
|
+
// }
|
|
29
|
+
|
|
30
|
+
export function getRoofMarginOutline(polygon){
|
|
31
|
+
let clockwiseNormalVector=polygon.normalVector
|
|
32
|
+
if(!polygon.isClockwise){
|
|
33
|
+
clockwiseNormalVector=multiplyVector(-1,clockwiseNormalVector)
|
|
34
|
+
}
|
|
35
|
+
//outterOutline is the outline close to the wall
|
|
36
|
+
polygon.margins.outterOutline=polygon.flatOutline
|
|
37
|
+
polygon.margins.innerOutline=[]
|
|
38
|
+
const length=polygon.outline.length
|
|
39
|
+
for(let index=0;index<length;index++){
|
|
40
|
+
const B=polygon.margins.outterOutline[index]
|
|
41
|
+
//A,B,C three consecutive points on the polygon.
|
|
42
|
+
//n is a vector in the plan normal to AB of length margin[A] directed towards the inside
|
|
43
|
+
//m is a vector in the plan normal to BC of length margin[B] directed towards the inside
|
|
44
|
+
//K is the margin outline linked with B
|
|
45
|
+
const A=polygon.margins.outterOutline[(index-1+length)%length]
|
|
46
|
+
const C=polygon.margins.outterOutline[(index+1)%length]
|
|
47
|
+
//filling the margins values if not initiated.
|
|
48
|
+
let marginA
|
|
49
|
+
let marginB
|
|
50
|
+
if(polygon.margins.isSameMargin){
|
|
51
|
+
marginA=polygon.margins.sameMargin
|
|
52
|
+
marginB=polygon.margins.sameMargin
|
|
53
|
+
polygon.margins.distances[index]=polygon.margins.sameMargin
|
|
54
|
+
}else{
|
|
55
|
+
if(!polygon.margins.distances[(index-1+length)%length]===undefined){
|
|
56
|
+
polygon.margins.distances[(index-1+length)%length]=polygon.margins.sameMargin
|
|
57
|
+
}
|
|
58
|
+
if(!polygon.margins.distances[index]===undefined){
|
|
59
|
+
polygon.margins.distances[index]=polygon.margins.sameMargin
|
|
60
|
+
}
|
|
61
|
+
marginA=polygon.margins.distances[(index-1+length)%length]
|
|
62
|
+
marginB=polygon.margins.distances[index]
|
|
63
|
+
}
|
|
64
|
+
let K=getMarginPoint(A,B,C,marginA,marginB,clockwiseNormalVector)
|
|
65
|
+
polygon.margins.innerOutline.push(K)
|
|
66
|
+
}
|
|
67
|
+
return polygon
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
export function getObstacleMarginOutline(polygon){
|
|
72
|
+
let clockwiseNormalVector=polygon.normalVector
|
|
73
|
+
if(polygon.isClockwise){
|
|
74
|
+
clockwiseNormalVector=multiplyVector(-1,clockwiseNormalVector)
|
|
75
|
+
}
|
|
76
|
+
//outterOutline is the outline close to the wall
|
|
77
|
+
polygon.margins.innerOutline=polygon.flatOutline
|
|
78
|
+
polygon.margins.outterOutline=[]
|
|
79
|
+
const length=polygon.margins.innerOutline.length
|
|
80
|
+
for(let index=0;index<length;index++){
|
|
81
|
+
const B=polygon.margins.innerOutline[index]
|
|
82
|
+
//A,B,C three consecutive points on the polygon.
|
|
83
|
+
//n is a vector in the plan normal to AB of length margin[A] directed towards the inside
|
|
84
|
+
//m is a vector in the plan normal to BC of length margin[B] directed towards the inside
|
|
85
|
+
//K is the margin outline linked with B
|
|
86
|
+
const A=polygon.margins.innerOutline[(index-1+length)%length]
|
|
87
|
+
const C=polygon.margins.innerOutline[(index+1)%length]
|
|
88
|
+
//filling the margins values if not initiated.
|
|
89
|
+
let marginA
|
|
90
|
+
let marginB
|
|
91
|
+
if(polygon.margins.isSameMargin){
|
|
92
|
+
marginA=polygon.margins.sameMargin
|
|
93
|
+
marginB=polygon.margins.sameMargin
|
|
94
|
+
polygon.margins.distances[index]=polygon.margins.sameMargin
|
|
95
|
+
}else{
|
|
96
|
+
if(polygon.margins.distances[(index-1+length)%length]===undefined){
|
|
97
|
+
polygon.margins.distances[(index-1+length)%length]=polygon.margins.sameMargin
|
|
98
|
+
}
|
|
99
|
+
if(polygon.margins.distances[index]===undefined){
|
|
100
|
+
polygon.margins.distances[index]=polygon.margins.sameMargin
|
|
101
|
+
}
|
|
102
|
+
marginA=polygon.margins.distances[(index-1+length)%length]
|
|
103
|
+
marginB=polygon.margins.distances[index]
|
|
104
|
+
}
|
|
105
|
+
let K=getMarginPoint(A,B,C,marginA,marginB,clockwiseNormalVector)
|
|
106
|
+
polygon.margins.outterOutline.push(K)
|
|
107
|
+
}
|
|
108
|
+
return polygon
|
|
109
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import {
|
|
2
|
+
isInsidePolygon,
|
|
3
|
+
verticalProjectionOnPlane,
|
|
4
|
+
} from '../../geometry'
|
|
5
|
+
//this function just update the "roof" and "moduleField(s)" properties of panels,moduleFields and roofs
|
|
6
|
+
export function UpdateRoofModuleFieldRelations(state) {
|
|
7
|
+
const roofPolygons = state.polygons.filter((poly) => poly.layer == 'roof')
|
|
8
|
+
const moduleFieldPolygons = state.polygons.filter(
|
|
9
|
+
(poly) => poly.layer == 'moduleField'
|
|
10
|
+
)
|
|
11
|
+
const panelPolygons = state.polygons.filter((poly) =>
|
|
12
|
+
['panel', 'user_deactivated_panel'].includes(poly.layer)
|
|
13
|
+
)
|
|
14
|
+
//let's remove all moduleFields from roofs
|
|
15
|
+
roofPolygons.forEach((roofPolygon) => {
|
|
16
|
+
//breaking circular reference for memory management
|
|
17
|
+
for (let k in roofPolygon.moduleFields) {
|
|
18
|
+
roofPolygon.moduleFields[k] = null
|
|
19
|
+
}
|
|
20
|
+
roofPolygon.moduleFields = []
|
|
21
|
+
})
|
|
22
|
+
moduleFieldPolygons.forEach((moduleFieldPolygon) => {
|
|
23
|
+
moduleFieldPolygon.roof = null
|
|
24
|
+
})
|
|
25
|
+
panelPolygons.forEach((panelPolygon) => {
|
|
26
|
+
panelPolygon.roof = null
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
for (let k in moduleFieldPolygons) {
|
|
30
|
+
const moduleField = moduleFieldPolygons[k]
|
|
31
|
+
//check if everypoint is in the same roof and if they are not too far from the flat surface.(at least above)
|
|
32
|
+
const roofPolygonCandidates = roofPolygons.filter((roof) =>
|
|
33
|
+
{
|
|
34
|
+
return moduleField.outline.every(p=>isInsidePolygon(p, roof.margins.innerOutline))
|
|
35
|
+
}
|
|
36
|
+
)
|
|
37
|
+
//roofs on roof can lead to multiple roof under a point. moduleField is in the smallest
|
|
38
|
+
let smallestRoof = null
|
|
39
|
+
for (let roof of roofPolygonCandidates) {
|
|
40
|
+
if (!smallestRoof || smallestRoof.area > roof.area) {
|
|
41
|
+
smallestRoof = roof
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
let roofPolygon = smallestRoof
|
|
45
|
+
|
|
46
|
+
if (!roofPolygon) {
|
|
47
|
+
//no roof found under first node of moduleField => moduleField will be deleted
|
|
48
|
+
continue
|
|
49
|
+
}
|
|
50
|
+
let moduleFieldBelongsToRoof = true
|
|
51
|
+
moduleField.outline.forEach((p) => {
|
|
52
|
+
const distanceToFlatRoof =
|
|
53
|
+
p.z -
|
|
54
|
+
verticalProjectionOnPlane(
|
|
55
|
+
p,
|
|
56
|
+
roofPolygon.normalVector,
|
|
57
|
+
roofPolygon.flatOutline[0]
|
|
58
|
+
).z
|
|
59
|
+
if (
|
|
60
|
+
!isInsidePolygon(p, roofPolygon.margins.innerOutline) ||
|
|
61
|
+
distanceToFlatRoof < -1 ||
|
|
62
|
+
distanceToFlatRoof > 50
|
|
63
|
+
) {
|
|
64
|
+
// one point from moduleField is not inside the roof or too far from it's flat surface => moduleField will be deleted
|
|
65
|
+
moduleFieldBelongsToRoof = false
|
|
66
|
+
}
|
|
67
|
+
})
|
|
68
|
+
if (moduleFieldBelongsToRoof) {
|
|
69
|
+
roofPolygon.moduleFields.push(moduleField)
|
|
70
|
+
moduleField.roof = roofPolygon
|
|
71
|
+
moduleField.panels.forEach((panel) => (panel.roof = roofPolygon))
|
|
72
|
+
panelPolygons.forEach((p) => {
|
|
73
|
+
if (p.moduleField.id == moduleField.id) {
|
|
74
|
+
p.roof = roofPolygon
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let newPolygons = state.polygons.map((p) => {
|
|
81
|
+
if (
|
|
82
|
+
!['roof', 'moduleField', 'panel', 'user_deactivated_panel'].includes(
|
|
83
|
+
p.layer
|
|
84
|
+
)
|
|
85
|
+
)
|
|
86
|
+
return p
|
|
87
|
+
if (p.layer == 'roof') return roofPolygons.find((r) => r.id == p.id)
|
|
88
|
+
if (p.layer == 'moduleField')
|
|
89
|
+
return moduleFieldPolygons.find((mf) => mf.id == p.id)
|
|
90
|
+
if (['panel', 'user_deactivated_panel'].includes(p.layer))
|
|
91
|
+
return panelPolygons.find((panel) => panel.id == p.id)
|
|
92
|
+
})
|
|
93
|
+
state = { ...state, polygons: newPolygons }
|
|
94
|
+
state = removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state)
|
|
95
|
+
return state
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function removeModuleFieldAndPanelsOnNoOrMultipleRoofs(state) {
|
|
99
|
+
const remainingPolygons = state.polygons.filter((polygon) =>
|
|
100
|
+
['roof', 'obstacle'].includes(polygon.layer)
|
|
101
|
+
)
|
|
102
|
+
const remainingModuleFields = state.polygons.filter(
|
|
103
|
+
(polygon) => polygon.layer == 'moduleField' && !!polygon.roof
|
|
104
|
+
)
|
|
105
|
+
const remainingPanels = state.polygons.filter(
|
|
106
|
+
(polygon) => polygon.layer == 'panel' && !!polygon.roof
|
|
107
|
+
)
|
|
108
|
+
const remainingUserDeactivatedPanels = state.polygons.filter(
|
|
109
|
+
(polygon) => polygon.layer == 'user_deactivated_panel' && !!polygon.roof
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
state.polygons = [
|
|
113
|
+
...remainingPolygons,
|
|
114
|
+
...remainingModuleFields,
|
|
115
|
+
...remainingPanels,
|
|
116
|
+
...remainingUserDeactivatedPanels
|
|
117
|
+
]
|
|
118
|
+
return state
|
|
119
|
+
}
|