-
Notifications
You must be signed in to change notification settings - Fork 5
/
layoutCostFunctions.py
318 lines (244 loc) · 10.2 KB
/
layoutCostFunctions.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
"""
"""
# -----------------------------------------------------------------------------------------
import numpy as np
import numpy.linalg as npla
# ==> should install pyvisgraph
import pyvisgraph as pvg
# import scipy as sp
import shapely.geometry.polygon as sgp
import shapely.geometry.linestring as sgls
import shapely.geometry.point as spt
import shapely.affinity as saf
def findPathPoly(sourceP, targetP, objList, layoutPoly):
"""
calculating shortest path from sourceP point to targetP that avoid polygon shape obstacles
sourceP/targetP - 2D points
objList - List of obstacle objects (polygons, each is a list of 2D points).
Should Contains the object's polygon and forward facing edge ???
layoutPoly - Nx2 list of ordered vertices defining a 2D polygon of N vertices - room polygon layout
last point NEQ first point
=>>>>>>> Assuming polygons DO NOT intersect
"""
nObj = len(objList)
objListVg = []
# Transform to pyvisgraph format
for n in range(nObj):
tmpPoly = []
for k in range(len(objList[n])):
tmpPoly.append(pvg.Point(objList[n][k][0], objList[n][k][1]))
objListVg.append(tmpPoly)
# Start building the visibility graph
graph = pvg.VisGraph()
refPoint = pvg.Point(sourceP[0][0], sourceP[0][1])
workers = 1
graph.build_mod(objListVg, workers, None, refPoint) # , workers=workers)
# graph.build(objListVg) #, workers=workers)
# Get the shortest path
shortest_path = []
path_distance = []
direct_distance = []
for n in range(len(sourceP)):
sP = pvg.Point(sourceP[n][0], sourceP[n][1])
tP = pvg.Point(targetP[n][0], targetP[n][1])
spath = graph.shortest_path(sP, tP)
# Calculate the total distance of the shortest path
pdistance = 0
prev_point = spath[0]
for point in spath[1:]:
pdistance += np.sqrt((prev_point.y - point.y) ** 2 + (prev_point.x - point.x) ** 2)
prev_point = point
shortest_path.append(spath)
path_distance.append(pdistance)
dDist = np.sqrt((targetP[n][0] - sourceP[n][0]) ** 2 + (targetP[n][1] - sourceP[n][1]) ** 2)
direct_distance.append(dDist)
# print('Shortest path distance: {}'.format(path_distance))
return shortest_path, path_distance, direct_distance
# ------------------------------------------------------------------------------------------
def calcLayoutClearance(objList, layoutPoly, entList):
"""
calculating layout polygons mean overlap
objList - List of obstacle objects (polygons)
Each object is assumed to represent the EXTENDED-bounding-box, i.e., including the extra gap
required around the object
layoutPoly - Nx2 list of ordered vertices defining a 2D polygon of N vertices - room polygon layout
last point NEQ first point
entList - List of entrance line segments (2D points). Entrances should not be occluded
"""
#
# =>>>>> CURRENTLY constraints are not included, e.g. entrance, window, power-outlet, TV
#
nObj = len(objList)
objListSp = []
# Transform to shapely
for n in range(nObj):
objListSp.append(sgp.Polygon(objList[n]))
ovlpSum = 0
for m in range(nObj - 1):
for n in range(nObj):
if m == n:
continue
ovlp = objListSp[m].intersection(objListSp[n]).area
ovlpSum += ovlp / objListSp[m].area
ovlpSum = ovlpSum / (nObj * (nObj - 1))
# ==> entrance overlap
# if entLine.touches(tmpPolyLayout) or entLine.intersects(tmpPolyLayout):
# ovlp = entLine.intersection(tmpPolyLayout).length / entLine.length
return ovlpSum
# ------------------------------------------------------------------------------------------
def calcLayoutCirculation(objList, srcList, tgtList):
"""
calculating layout polygons accessibility from entrance (could be more than one entrance)
objList - List of obstacle objects (polygons)
Each object is assumed to represent the EXTENDED-bounding-box, i.e., including the extra gap
required around the object
src/tgt-List - pairs of points between which shortest path is calculated and compared to straight path
"""
nPairs = len(srcList)
pathRatioSum = 0
sPath, lenPath, dirPath = findPathPoly(srcList, tgtList, objList, [])
for n in range(nPairs):
pathRatioSum += (1 - dirPath[n] / lenPath[n])
return pathRatioSum
# ------------------------------------------------------------------------------------------
def calcGroupRelation(objPos, membershipVec, dR):
"""
calculating object inter-group-relations: spread of objects from a group relative to space diagonal
objPos - vector of objects' center (numpy array)
membershipVec - vector of objects' membership association (integers)
dR - space diagonal (scalar)
"""
gSum = 0
nObj = len(objPos)
for i in range(nObj - 1):
for j in range(i + 1, nObj):
gSum += 1.0 * (not (membershipVec[i] - membershipVec[j])) * npla.norm(np.array(objPos[i]) - np.array(objPos[j]))
gSum /= ((nObj - 1) * nObj / 2 * dR)
return gSum
# ------------------------------------------------------------------------------------------
#### Remaining ##
def calcAlignment(backPos, walls, wallProbVec, dR):
"""
calculating object alignment, currently only w.r.t. supporting wall
backPos - vector of objects' back position (numpy array)
walls - list of walls, each represented as a line (end points)
wallProbVec - probability vector of objects' to stand against the wall
dR - space diagonal (scalar)
"""
#
# ====> DIDNOT check direction, i.e. that object is parallel/perpendicular to wall
#
nW = len(walls)
nO = len(backPos)
wLines = []
for iW in range(nW):
wLines.append(sgls.LineString((walls[iW][0], walls[iW][1])))
wSum = 0
for iO in range(nO):
dP = np.array([])
for iW in range(nW):
# shortest distance to wall
dP = np.append(dP, wLines[iW].distance(spt.Point(backPos[iO])))
wSum += wallProbVec[iO] * min(dP)
wSum /= (nO * dR)
return wSum
# ------------------------------------------------------------------------------------------
def calcObjDistrib(objPos):
"""
calculating object distribution in space, also referred to as Rhythm
"""
nObj = len(objPos)
# get all pairs distance
dP = np.array([])
for i in range(nObj - 1):
for j in range(i + 1, nObj):
dP = np.append(dP, npla.norm(objPos[i] - objPos[j]))
dMx = np.max(dP)
dP /= dMx
dPmean = np.median(dP)
dSum = 0
for n in range(len(dP)):
dSum += (dP[n] - dPmean) ** 2
dSum /= len(dP) # ((nObj-1)*nObj/2)
# ==>>>> Maybe calculate sqrt(dSum), i.e. the Sigma and not Variance
return dSum
# ------------------------------------------------------------------------------------------
def calcViewFrust(objPairsPos, objPairsDir):
"""
calculating viewing frustum of some pairs of objects, i.e., those objects must "see" each other
Should take into account direction of objects (facing each other)
"""
# ------------------------------------------------------------------------------------------
def calcFunctionality(impVec, objCatNum, catDesNum):
"""
calculating objects functionality importance and quantity
impVec - vector of importance values
objCatNum - amount of objects from each category in the layout (dict)
catDesNum - desired amount of objects from each category (dict)
"""
nO = len(impVec)
fSum1 = np.sum(1. - impVec)
fSum1 /= nO
fSum2 = 0
for oc in objCatNum.keys():
fSum2 += abs(objCatNum[oc] - catDesNum[oc])
fSum2 /= (1.0 * len(objCatNum))
fSum = 0.5 * (fSum1 + fSum2)
return fSum
# ------------------------------------------------------------------------------------------
def calcProportion(objVol, roomVol, desRatio):
"""
calculating layout-volume-to-room-ratio
objVol: array of objects' area
roomVol: room area
"""
objVolSum = 0
for i in range(len(objVol)):
objVolSum += objVol[i]
gP = max(desRatio - 1.0 * objVolSum / roomVol, 0) / (1.0 * desRatio)
return gP
# ------------------------------------------------------------------------------------------
def calcGoldenSec(objPos, roomRect, dR):
"""
calculating objects location w.r.t. golden section lines
objPos: objects' center position
roomRect: 4 points of room (or sub-area) rectangle
dR: room diagonal
"""
# make sure the vertices are ordered
tmpRect = sgp.Polygon(roomRect)
tmpRect = tmpRect.convex_hull
t_rect = tmpRect.exterior.coords[0:-1]
# creating golden lines. Assuming gsRatio = 13/21
# go over the 2 consecutive pair of vertices and generate the 4-lines, 2 in each side
gsr = 13.0 / 21.0
line1 = sgls.LineString((t_rect[0], t_rect[1]))
length = npla.norm(np.array(t_rect[0]) - np.array(t_rect[1]))
pt11 = line1.interpolate(length * (1.0 - gsr))
pt12 = line1.interpolate(length * gsr)
line3 = sgls.LineString((t_rect[2], t_rect[3]))
length = npla.norm(np.array(t_rect[2]) - np.array(t_rect[3]))
pt32 = line3.interpolate(length * (1.0 - gsr))
pt31 = line3.interpolate(length * gsr)
line2 = sgls.LineString((t_rect[1], t_rect[2]))
length = npla.norm(np.array(t_rect[1]) - np.array(t_rect[2]))
pt21 = line2.interpolate(length * (1.0 - gsr))
pt22 = line2.interpolate(length * gsr)
line4 = sgls.LineString((t_rect[3], t_rect[0]))
length = npla.norm(np.array(t_rect[3]) - np.array(t_rect[0]))
pt42 = line4.interpolate(length * (1.0 - gsr))
pt41 = line4.interpolate(length * gsr)
gsLines = []
gsLines.append(sgls.LineString((pt11, pt31)))
gsLines.append(sgls.LineString((pt12, pt32)))
gsLines.append(sgls.LineString((pt21, pt41)))
gsLines.append(sgls.LineString((pt22, pt42)))
dObjGs = []
for i in range(len(objPos)):
dd = []
for j in range(len(gsLines)):
dd.append(gsLines[j].distance(spt.Point(objPos[i])))
dObjGs.append(min(dd))
gP = np.sum(dObjGs)
gP /= (1.0 * dR * len(objPos))
return gP