@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.
@@ -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,2 @@
1
+ export * from './AddMargin'
2
+ export * from './updateComputedGeometryPolygon'
@@ -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
+ }
@@ -0,0 +1,8 @@
1
+ export * from './Point'
2
+ export * from './Line'
3
+ export * from './Polygon'
4
+ export * from './Circle'
5
+ export * from './hydrate'
6
+
7
+ export * from './derivedState'
8
+ export * from './graph/graphCreation'