@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,86 @@
|
|
|
1
|
+
import {
|
|
2
|
+
verticalProjectionOnPlane,
|
|
3
|
+
calculateArea
|
|
4
|
+
} from '../../geometry'
|
|
5
|
+
import {
|
|
6
|
+
intersectOutlines
|
|
7
|
+
} from '../../intersectionPolygon'
|
|
8
|
+
//this function just update the "roof" and "holes" properties of roofs and obstacles
|
|
9
|
+
export function UpdateRoofObstacleRelations(state,updateVersionEnable=true) {
|
|
10
|
+
//let's remove all holes from roofs which are not obstacle then check for every obstacle and add them to roofs
|
|
11
|
+
const roofPolygons = state.polygons.filter((poly) => poly.layer == 'roof')
|
|
12
|
+
const initialRelation={}
|
|
13
|
+
|
|
14
|
+
roofPolygons.forEach((roofPolygon) => {
|
|
15
|
+
initialRelation[roofPolygon.id]=[]
|
|
16
|
+
//breaking circular reference for memory management
|
|
17
|
+
for (let k in roofPolygon.holes) {
|
|
18
|
+
initialRelation[roofPolygon.id].push(roofPolygon.holes.id)
|
|
19
|
+
roofPolygon.holes[k] = null
|
|
20
|
+
}
|
|
21
|
+
roofPolygon.holes = []
|
|
22
|
+
})
|
|
23
|
+
const holePolygons = state.polygons.filter((poly) =>
|
|
24
|
+
['roof','obstacle'].includes(poly.layer)
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
holePolygons.forEach((obstacle) => {
|
|
28
|
+
//breaking circular reference for memory management
|
|
29
|
+
for (let k in obstacle.roofs) {
|
|
30
|
+
obstacle.roofs[k] = null
|
|
31
|
+
}
|
|
32
|
+
obstacle.roofs = []
|
|
33
|
+
|
|
34
|
+
let roofsWithObstacle=roofPolygons.filter(roof=>{
|
|
35
|
+
let intersection=intersectOutlines(obstacle.outline,roof.outline)[0]||[]
|
|
36
|
+
if(intersection.length==0)return false
|
|
37
|
+
if(obstacle.layer=='obstacle')return true
|
|
38
|
+
|
|
39
|
+
let deltaArea=calculateArea(roof.outline)-calculateArea(intersection)
|
|
40
|
+
if(deltaArea<100)return false
|
|
41
|
+
return true
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
let heightReference = 0
|
|
45
|
+
for (let roof of roofsWithObstacle) {
|
|
46
|
+
|
|
47
|
+
heightReference = obstacle.outline.reduce((acc,cur)=>{
|
|
48
|
+
acc=Math.max(
|
|
49
|
+
acc,
|
|
50
|
+
verticalProjectionOnPlane(cur, roof.normalVector, roof.flatOutline[0])
|
|
51
|
+
.z
|
|
52
|
+
)
|
|
53
|
+
return acc
|
|
54
|
+
},0)
|
|
55
|
+
|
|
56
|
+
if (!obstacle.roofs.map((r) => r.id).includes(roof.id) && obstacle.id!=roof.id) {
|
|
57
|
+
obstacle.roofs.push(roof)
|
|
58
|
+
}
|
|
59
|
+
if (!roof.holes.map((h) => h.id).includes(obstacle.id) && obstacle.id!=roof.id) {
|
|
60
|
+
roof.holes.push(obstacle)
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
obstacle.heightReference = heightReference
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
roofPolygons.forEach((roof)=>{
|
|
69
|
+
if(roof.holes.map(h=>h.id).sort().join()!=initialRelation[roof.id].sort().join()){
|
|
70
|
+
if(updateVersionEnable){
|
|
71
|
+
roof.version++
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
state = removeStandaloneObstacles(state)
|
|
77
|
+
|
|
78
|
+
return state
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function removeStandaloneObstacles(state) {
|
|
82
|
+
state.polygons = state.polygons.filter(
|
|
83
|
+
(polygon) => polygon.layer != 'obstacle' || polygon.roofs.length > 0
|
|
84
|
+
)
|
|
85
|
+
return state
|
|
86
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import {
|
|
2
|
+
meanVector,
|
|
3
|
+
substractVector,
|
|
4
|
+
multiplyVector
|
|
5
|
+
} from '../../vector'
|
|
6
|
+
import {
|
|
7
|
+
verticalProjectionOnPlane,
|
|
8
|
+
inclineWithNormalVector,
|
|
9
|
+
directionWithNormalVector,
|
|
10
|
+
isClockWise,
|
|
11
|
+
calculateArea
|
|
12
|
+
} from '../../geometry'
|
|
13
|
+
import { maximumGapLimit } from '../../config'
|
|
14
|
+
import { SVD } from 'svd-js'
|
|
15
|
+
import {updateMarginOutlinePolygon} from './AddMargin'
|
|
16
|
+
|
|
17
|
+
//This function calculate derived field from polygon outline and margin outline
|
|
18
|
+
export function updateComputedGeometryState(state) {
|
|
19
|
+
state.polygons.forEach(polygon=>{
|
|
20
|
+
polygon=updateComputedGeometryPolygon(polygon)
|
|
21
|
+
})
|
|
22
|
+
return state
|
|
23
|
+
}
|
|
24
|
+
export function updateOutlineFromInclineDirection(incline, direction, outline,initialAverageHeight) {
|
|
25
|
+
const newNormalVector = {}
|
|
26
|
+
newNormalVector.x =
|
|
27
|
+
Math.sin((incline * Math.PI) / 180) * Math.sin((direction * Math.PI) / 180)
|
|
28
|
+
newNormalVector.y =
|
|
29
|
+
Math.sin((incline * Math.PI) / 180) * Math.cos((direction * Math.PI) / 180)
|
|
30
|
+
newNormalVector.z = Math.cos((incline * Math.PI) / 180)
|
|
31
|
+
let meanPoint = meanVector(outline)
|
|
32
|
+
meanPoint.z=initialAverageHeight
|
|
33
|
+
outline = outline.map((p) => {
|
|
34
|
+
return verticalProjectionOnPlane(p, newNormalVector, meanPoint)
|
|
35
|
+
})
|
|
36
|
+
//if some points are with negative altitude, we offset the whole roof
|
|
37
|
+
const altitudeOffset = Math.min(...outline.map((p) => p.z))
|
|
38
|
+
if (altitudeOffset < 0) {
|
|
39
|
+
outline.forEach((p) => (p.z -= altitudeOffset))
|
|
40
|
+
}
|
|
41
|
+
//if some points are with altitude>50000, we limit those points
|
|
42
|
+
outline.forEach((p) => (p.z = Math.max(0, Math.min(p.z, 50000))))
|
|
43
|
+
|
|
44
|
+
return outline
|
|
45
|
+
}
|
|
46
|
+
export function updateComputedGeometryPolygon(polygon) {
|
|
47
|
+
if(!['roof','obstacle','moduleField'].includes(polygon.layer)){
|
|
48
|
+
return polygon
|
|
49
|
+
}
|
|
50
|
+
const {
|
|
51
|
+
projectedOutline,
|
|
52
|
+
maximumGap,
|
|
53
|
+
normalVector,
|
|
54
|
+
meanPoint,
|
|
55
|
+
incline,
|
|
56
|
+
direction
|
|
57
|
+
} = calculateBestFittingPlanePolygon(polygon.outline)
|
|
58
|
+
polygon.flatOutline = projectedOutline
|
|
59
|
+
polygon.maximumGap = maximumGap
|
|
60
|
+
polygon.isFlat = maximumGap < maximumGapLimit
|
|
61
|
+
polygon.normalVector = normalVector
|
|
62
|
+
polygon.meanPoint = meanPoint
|
|
63
|
+
polygon.incline = incline
|
|
64
|
+
polygon.direction = direction
|
|
65
|
+
polygon.area=calculateArea(polygon.flatOutline)/1000000
|
|
66
|
+
polygon.isClockwise = isClockWise(polygon.outline)
|
|
67
|
+
polygon=updateMarginOutlinePolygon(polygon)
|
|
68
|
+
|
|
69
|
+
return polygon
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function calculateBestFittingPlanePolygon(fullOutline) {
|
|
73
|
+
if (fullOutline.length < 3) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
let isIncludingAllNodes=false
|
|
77
|
+
let outline=fullOutline.filter(p=>!p.selected)
|
|
78
|
+
if(outline.length<3){
|
|
79
|
+
isIncludingAllNodes=true
|
|
80
|
+
outline=fullOutline
|
|
81
|
+
}
|
|
82
|
+
const meanPoint = meanVector(outline)
|
|
83
|
+
|
|
84
|
+
const centralizedOutline = outline.map((p) => substractVector(p, meanPoint))
|
|
85
|
+
const a = centralizedOutline.map((p) => [p.x, p.y, p.z])
|
|
86
|
+
|
|
87
|
+
let { v, q } = SVD(a, false)
|
|
88
|
+
let vt = [[], [], []]
|
|
89
|
+
for (let i in v) {
|
|
90
|
+
for (let j in v[i]) {
|
|
91
|
+
vt[j][i] = v[i][j]
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
v = vt
|
|
95
|
+
let normalVectorIndex=0
|
|
96
|
+
for(let index in q){
|
|
97
|
+
if(!isNaN(q[index]) && q[index]<q[normalVectorIndex]){
|
|
98
|
+
normalVectorIndex=index
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
let normalVectorArray = v[normalVectorIndex]
|
|
102
|
+
let normalVector = {x:0,y:0,z:1}
|
|
103
|
+
if(!normalVectorArray){
|
|
104
|
+
console.error("no normalVector found for:q",q,"v",v,"meanPoint",meanPoint,"a",a,'normal index',normalVectorIndex,'v',v,'outline',outline)
|
|
105
|
+
}else{
|
|
106
|
+
normalVector.x = normalVectorArray[0]
|
|
107
|
+
normalVector.y = normalVectorArray[1]
|
|
108
|
+
normalVector.z = normalVectorArray[2]
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if (normalVector.z < 0) {
|
|
112
|
+
normalVector = multiplyVector(-1, normalVector)
|
|
113
|
+
}
|
|
114
|
+
//gard to prevent crazy inclinaison roof
|
|
115
|
+
if(normalVector.z<0.1){
|
|
116
|
+
normalVector = {x:0,y:0,z:1}
|
|
117
|
+
}
|
|
118
|
+
//plane equation is ax+by+cz=d
|
|
119
|
+
//where normalvector=(a,b,c)
|
|
120
|
+
//let's calculate d knowing our plane passes by the point with the
|
|
121
|
+
const projectedOutline = fullOutline.map((p) =>{
|
|
122
|
+
const proj=verticalProjectionOnPlane(p, normalVector, meanPoint)
|
|
123
|
+
return {
|
|
124
|
+
x:proj.x,
|
|
125
|
+
y:proj.y,
|
|
126
|
+
z:Math.max(0,Math.min(50000,proj.z))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
)
|
|
130
|
+
const maximumGap = Math.max(
|
|
131
|
+
...fullOutline.map((p, index) => {
|
|
132
|
+
let gap=Math.abs(p.z - projectedOutline[index].z)
|
|
133
|
+
return gap
|
|
134
|
+
})
|
|
135
|
+
)
|
|
136
|
+
//let's find the the biggest positive gap to reposition the plan passing by this point in order to have an always visible plane
|
|
137
|
+
|
|
138
|
+
let biggestPositivGap = Math.max(
|
|
139
|
+
...fullOutline.map((p, index) => p.selected && !isIncludingAllNodes ? 0:p.z - projectedOutline[index].z)
|
|
140
|
+
)
|
|
141
|
+
projectedOutline.forEach((p) => (p.z += biggestPositivGap))
|
|
142
|
+
|
|
143
|
+
//if less than 3 unselected node=>flat roof on top.
|
|
144
|
+
const biggestAbsoluteGap = Math.max(
|
|
145
|
+
...fullOutline.map((p, index) => p.selected ? 0:Math.abs(p.z - projectedOutline[index].z))
|
|
146
|
+
)
|
|
147
|
+
for(let index in fullOutline){
|
|
148
|
+
let p=fullOutline[index]
|
|
149
|
+
let hasWarning=(Math.abs(p.z-projectedOutline[index].z)==biggestAbsoluteGap && biggestAbsoluteGap>maximumGapLimit)
|
|
150
|
+
//I add warning if hasWarning and I remove it if shift is smaller then maxGapLimit. Otherwise, I do not change anything
|
|
151
|
+
p.hasWarning=hasWarning?
|
|
152
|
+
hasWarning:
|
|
153
|
+
Math.abs(p.z-projectedOutline[index].z)<maximumGapLimit?
|
|
154
|
+
false:p.hasWarning
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
//set warning to the furthest
|
|
158
|
+
const incline = inclineWithNormalVector(normalVector)
|
|
159
|
+
const direction = directionWithNormalVector(normalVector)
|
|
160
|
+
return {
|
|
161
|
+
projectedOutline,
|
|
162
|
+
maximumGap,
|
|
163
|
+
normalVector,
|
|
164
|
+
meanPoint,
|
|
165
|
+
incline,
|
|
166
|
+
direction
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
// Javascript program to print DFS
|
|
3
|
+
// traversal from a given
|
|
4
|
+
// graph
|
|
5
|
+
|
|
6
|
+
// This class represents a
|
|
7
|
+
// directed graph using adjacency
|
|
8
|
+
// list representation
|
|
9
|
+
export class Graph
|
|
10
|
+
{
|
|
11
|
+
|
|
12
|
+
// Constructor
|
|
13
|
+
//v=number of vertices
|
|
14
|
+
constructor()
|
|
15
|
+
{
|
|
16
|
+
this.visited=[]
|
|
17
|
+
this.dict= []
|
|
18
|
+
this.adj = []
|
|
19
|
+
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Function to add an edge into the graph
|
|
23
|
+
addEdge(uuid1, uuid2)
|
|
24
|
+
{
|
|
25
|
+
let v=this.dict.indexOf(uuid1)
|
|
26
|
+
let length
|
|
27
|
+
if(v==-1){
|
|
28
|
+
length=this.dict.length
|
|
29
|
+
this.dict[length]=uuid1
|
|
30
|
+
this.adj[length]=[]
|
|
31
|
+
v=length
|
|
32
|
+
}
|
|
33
|
+
let w=this.dict.indexOf(uuid2)
|
|
34
|
+
if(w==-1){
|
|
35
|
+
length=this.dict.length
|
|
36
|
+
this.dict[length]=uuid2
|
|
37
|
+
this.adj[length]=[]
|
|
38
|
+
w=length
|
|
39
|
+
}
|
|
40
|
+
// Add w to v's list.
|
|
41
|
+
if(!this.adj[v]){this.adj[v]=[]}
|
|
42
|
+
this.adj[v].push(w);
|
|
43
|
+
if(!this.adj[w]){this.adj[w]=[]}
|
|
44
|
+
this.adj[w].push(v);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// A function used by DFS
|
|
48
|
+
DFSUtil(v, visited)
|
|
49
|
+
{
|
|
50
|
+
|
|
51
|
+
// Mark the current node as visited and print it
|
|
52
|
+
visited[v] = true;
|
|
53
|
+
this.visited.push(v)
|
|
54
|
+
|
|
55
|
+
// Recur for all the vertices adjacent to this
|
|
56
|
+
// vertex
|
|
57
|
+
for(let i in this.adj[v])
|
|
58
|
+
{
|
|
59
|
+
let n = this.adj[v][i]
|
|
60
|
+
if (!visited[n])
|
|
61
|
+
this.DFSUtil(n, visited);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// The function to do DFS traversal.
|
|
66
|
+
// It uses recursive
|
|
67
|
+
// DFSUtil()
|
|
68
|
+
DFS(uuidStart){
|
|
69
|
+
let v=this.dict.indexOf(uuidStart)
|
|
70
|
+
// Mark all the vertices as
|
|
71
|
+
// not visited(set as
|
|
72
|
+
// false by default in java)
|
|
73
|
+
let visited = [];
|
|
74
|
+
// Call the recursive helper
|
|
75
|
+
// function to print DFS
|
|
76
|
+
// traversal
|
|
77
|
+
this.DFSUtil(v, visited);
|
|
78
|
+
return [...new Set(this.visited.map((i)=>{return this.dict[i]}))]
|
|
79
|
+
}
|
|
80
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import {Graph} from './DFS'
|
|
2
|
+
import {
|
|
3
|
+
isSameSegment2D,
|
|
4
|
+
} from '../../geometry'
|
|
5
|
+
|
|
6
|
+
export function generateGraph(polygons){
|
|
7
|
+
const edges2D=[]
|
|
8
|
+
const g=new Graph()
|
|
9
|
+
polygons
|
|
10
|
+
.filter((poly) => poly.layer=='roof')
|
|
11
|
+
.forEach((polygon,index) => {
|
|
12
|
+
g.dict.push(polygon.id)
|
|
13
|
+
const outline = polygon.outline
|
|
14
|
+
outline.forEach((point, i) => {
|
|
15
|
+
//check if edge2D already exist
|
|
16
|
+
const nextPoint = outline[(i + 1) % outline.length]
|
|
17
|
+
let edge2D = edges2D.find((edge) => {
|
|
18
|
+
return (
|
|
19
|
+
isSameSegment2D([point, nextPoint], edge.outline)
|
|
20
|
+
)
|
|
21
|
+
})
|
|
22
|
+
if (!edge2D) {
|
|
23
|
+
//create an edge at this position
|
|
24
|
+
edge2D = {}
|
|
25
|
+
edge2D.outline=[point, nextPoint]
|
|
26
|
+
edge2D.belongsTo = []
|
|
27
|
+
edges2D.push(edge2D)
|
|
28
|
+
}
|
|
29
|
+
//add BelongToPolygon
|
|
30
|
+
edge2D.belongsTo.push({
|
|
31
|
+
polygonId: polygon.id,
|
|
32
|
+
index: i,
|
|
33
|
+
polygon
|
|
34
|
+
})
|
|
35
|
+
})
|
|
36
|
+
})
|
|
37
|
+
for(let k in edges2D){
|
|
38
|
+
let edge=edges2D[k]
|
|
39
|
+
if(edge.belongsTo.length==2){
|
|
40
|
+
g.addEdge(edge.belongsTo[0].polygonId,edge.belongsTo[1].polygonId)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return g
|
|
44
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export function hydratePolygon(serializedPolygon){
|
|
2
|
+
const layer=serializedPolygon.layer
|
|
3
|
+
let polygon=new Polygon(serializedPolygon.outline,layer)
|
|
4
|
+
polygon.id=serializedPolygon.id
|
|
5
|
+
polygon.name=serializedPolygon.name
|
|
6
|
+
polygon.margins=serializedPolygon.margins
|
|
7
|
+
if(layer=="obstacle"){
|
|
8
|
+
polygon.isParallel=serializedPolygon.isParallel
|
|
9
|
+
polygon.height=serializedPolygon.height
|
|
10
|
+
}else if(layer=="moduleField"){
|
|
11
|
+
polygon.data=serializedPolygon.data
|
|
12
|
+
polygon.pvData=serializedPolygon.pvData
|
|
13
|
+
polygon.mountingData=serializedPolygon.mountingData
|
|
14
|
+
polygon.panels=[]
|
|
15
|
+
polygon.userDeactivatedPanels=[]
|
|
16
|
+
}else if(layer=="panel"){
|
|
17
|
+
polygon.index=serializedPolygon.index
|
|
18
|
+
polygon.moduleField=serializedPolygon.moduleField
|
|
19
|
+
}else if(layer=="user_deactivated_panel"){
|
|
20
|
+
polygon.index=serializedPolygon.index
|
|
21
|
+
polygon.moduleField=serializedPolygon.moduleField
|
|
22
|
+
}
|
|
23
|
+
return polygon
|
|
24
|
+
}
|