@a2f0/skyline 0.0.0-stage → 0.1.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.
Files changed (195) hide show
  1. package/NOTICE.md +20 -0
  2. package/README.md +52 -2
  3. package/docs/package.md +222 -0
  4. package/docs/skyline-geography.md +503 -0
  5. package/lib/models/aon-center.d.ts +4 -0
  6. package/lib/models/aon-center.js +60 -0
  7. package/lib/models/aon-geographic.d.ts +9 -0
  8. package/lib/models/aon-geographic.js +35 -0
  9. package/lib/models/aon-tower.d.ts +23 -0
  10. package/lib/models/aon-tower.js +130 -0
  11. package/lib/models/blue-cross-geographic.d.ts +14 -0
  12. package/lib/models/blue-cross-geographic.js +248 -0
  13. package/lib/models/borg-warner-geographic.d.ts +20 -0
  14. package/lib/models/borg-warner-geographic.js +143 -0
  15. package/lib/models/buckingham-geographic.d.ts +10 -0
  16. package/lib/models/buckingham-geographic.js +121 -0
  17. package/lib/models/building-kit.d.ts +84 -0
  18. package/lib/models/building-kit.js +358 -0
  19. package/lib/models/celebrations.d.ts +39 -0
  20. package/lib/models/celebrations.js +44 -0
  21. package/lib/models/chicago-athletic-association-geographic.d.ts +32 -0
  22. package/lib/models/chicago-athletic-association-geographic.js +173 -0
  23. package/lib/models/crain-communications.d.ts +5 -0
  24. package/lib/models/crain-communications.js +75 -0
  25. package/lib/models/crain-geographic.d.ts +10 -0
  26. package/lib/models/crain-geographic.js +69 -0
  27. package/lib/models/crain-tower.d.ts +32 -0
  28. package/lib/models/crain-tower.js +378 -0
  29. package/lib/models/facade-grid.d.ts +18 -0
  30. package/lib/models/facade-grid.js +176 -0
  31. package/lib/models/gage-geographic.d.ts +19 -0
  32. package/lib/models/gage-geographic.js +154 -0
  33. package/lib/models/heritage-at-millennium-park.d.ts +3 -0
  34. package/lib/models/heritage-at-millennium-park.js +284 -0
  35. package/lib/models/heritage-geographic.d.ts +11 -0
  36. package/lib/models/heritage-geographic.js +208 -0
  37. package/lib/models/hyatt-west-tower-geographic.d.ts +26 -0
  38. package/lib/models/hyatt-west-tower-geographic.js +86 -0
  39. package/lib/models/keith-ascher-geographic.d.ts +29 -0
  40. package/lib/models/keith-ascher-geographic.js +110 -0
  41. package/lib/models/kemper-geographic.d.ts +8 -0
  42. package/lib/models/kemper-geographic.js +153 -0
  43. package/lib/models/kemper.d.ts +2 -0
  44. package/lib/models/kemper.js +78 -0
  45. package/lib/models/lake-view-geographic.d.ts +15 -0
  46. package/lib/models/lake-view-geographic.js +134 -0
  47. package/lib/models/maclean-center-geographic.d.ts +20 -0
  48. package/lib/models/maclean-center-geographic.js +162 -0
  49. package/lib/models/michigan-boulevard-geographic.d.ts +12 -0
  50. package/lib/models/michigan-boulevard-geographic.js +157 -0
  51. package/lib/models/michigan-plaza-south-geographic.d.ts +7 -0
  52. package/lib/models/michigan-plaza-south-geographic.js +84 -0
  53. package/lib/models/michigan-plaza-south.d.ts +2 -0
  54. package/lib/models/michigan-plaza-south.js +68 -0
  55. package/lib/models/millennium-park-plaza-geographic.d.ts +9 -0
  56. package/lib/models/millennium-park-plaza-geographic.js +103 -0
  57. package/lib/models/monroe-geographic.d.ts +19 -0
  58. package/lib/models/monroe-geographic.js +177 -0
  59. package/lib/models/north-michigan-180-geographic.d.ts +14 -0
  60. package/lib/models/north-michigan-180-geographic.js +117 -0
  61. package/lib/models/north-wabash-geographic.d.ts +14 -0
  62. package/lib/models/north-wabash-geographic.js +122 -0
  63. package/lib/models/on-the-park-geographic.d.ts +11 -0
  64. package/lib/models/on-the-park-geographic.js +245 -0
  65. package/lib/models/one-prudential-geographic.d.ts +12 -0
  66. package/lib/models/one-prudential-geographic.js +55 -0
  67. package/lib/models/one-prudential-plaza.d.ts +5 -0
  68. package/lib/models/one-prudential-plaza.js +72 -0
  69. package/lib/models/one-prudential-tower.d.ts +45 -0
  70. package/lib/models/one-prudential-tower.js +246 -0
  71. package/lib/models/peoples-gas-geographic.d.ts +23 -0
  72. package/lib/models/peoples-gas-geographic.js +250 -0
  73. package/lib/models/railway-exchange-geographic.d.ts +18 -0
  74. package/lib/models/railway-exchange-geographic.js +176 -0
  75. package/lib/models/river-plaza-geographic.d.ts +16 -0
  76. package/lib/models/river-plaza-geographic.js +104 -0
  77. package/lib/models/sheraton-grand-geographic.d.ts +24 -0
  78. package/lib/models/sheraton-grand-geographic.js +112 -0
  79. package/lib/models/six-north-michigan-geographic.d.ts +16 -0
  80. package/lib/models/six-north-michigan-geographic.js +180 -0
  81. package/lib/models/skyline-geography-data.d.ts +44 -0
  82. package/lib/models/skyline-geography-data.js +607 -0
  83. package/lib/models/skyline-geography.d.ts +23 -0
  84. package/lib/models/skyline-geography.js +322 -0
  85. package/lib/models/swissotel-geographic.d.ts +24 -0
  86. package/lib/models/swissotel-geographic.js +79 -0
  87. package/lib/models/three-illinois-center-geographic.d.ts +26 -0
  88. package/lib/models/three-illinois-center-geographic.js +86 -0
  89. package/lib/models/trump-geographic.d.ts +14 -0
  90. package/lib/models/trump-geographic.js +51 -0
  91. package/lib/models/trump-international-tower.d.ts +4 -0
  92. package/lib/models/trump-international-tower.js +84 -0
  93. package/lib/models/trump-tower.d.ts +43 -0
  94. package/lib/models/trump-tower.js +229 -0
  95. package/lib/models/two-illinois-center-geographic.d.ts +26 -0
  96. package/lib/models/two-illinois-center-geographic.js +85 -0
  97. package/lib/models/two-prudential-geographic.d.ts +25 -0
  98. package/lib/models/two-prudential-geographic.js +47 -0
  99. package/lib/models/two-prudential-plaza.d.ts +8 -0
  100. package/lib/models/two-prudential-plaza.js +55 -0
  101. package/lib/models/two-prudential-tower.d.ts +53 -0
  102. package/lib/models/two-prudential-tower.js +441 -0
  103. package/lib/models/university-club-geographic.d.ts +24 -0
  104. package/lib/models/university-club-geographic.js +217 -0
  105. package/lib/models/willoughby-tower-geographic.d.ts +17 -0
  106. package/lib/models/willoughby-tower-geographic.js +195 -0
  107. package/lib/models/window-illumination.d.ts +14 -0
  108. package/lib/models/window-illumination.js +64 -0
  109. package/lib/package-assets.d.ts +5 -0
  110. package/lib/package-assets.js +9 -0
  111. package/lib/skyline-package.d.ts +23 -0
  112. package/lib/skyline-package.js +25 -0
  113. package/lib/skyline-scene.d.ts +25 -0
  114. package/lib/skyline-scene.js +86 -0
  115. package/lib/study-types.d.ts +49 -0
  116. package/lib/study-types.js +1 -0
  117. package/lib/vendor/three-r186.d.ts +1 -0
  118. package/lib/vendor/three-r186.js +1 -0
  119. package/package.json +64 -4
  120. package/site/building-detail.html +54 -0
  121. package/site/building-detail.js +84 -0
  122. package/site/building-study.html +58 -0
  123. package/site/building-study.js +3 -0
  124. package/site/index.html +407 -0
  125. package/site/models/aon-center.js +60 -0
  126. package/site/models/aon-geographic.js +35 -0
  127. package/site/models/aon-tower.js +130 -0
  128. package/site/models/blue-cross-geographic.js +248 -0
  129. package/site/models/borg-warner-geographic.js +143 -0
  130. package/site/models/buckingham-geographic.js +121 -0
  131. package/site/models/building-kit.js +358 -0
  132. package/site/models/celebrations.js +44 -0
  133. package/site/models/chicago-athletic-association-geographic.js +173 -0
  134. package/site/models/crain-communications.js +75 -0
  135. package/site/models/crain-geographic.js +69 -0
  136. package/site/models/crain-reference.svg +10 -0
  137. package/site/models/crain-tower.js +378 -0
  138. package/site/models/facade-grid.js +176 -0
  139. package/site/models/gage-geographic.js +154 -0
  140. package/site/models/heritage-at-millennium-park.js +284 -0
  141. package/site/models/heritage-geographic.js +208 -0
  142. package/site/models/hyatt-west-tower-geographic.js +86 -0
  143. package/site/models/keith-ascher-geographic.js +110 -0
  144. package/site/models/kemper-geographic.js +153 -0
  145. package/site/models/kemper.js +78 -0
  146. package/site/models/lake-view-geographic.js +134 -0
  147. package/site/models/maclean-center-geographic.js +162 -0
  148. package/site/models/michigan-boulevard-geographic.js +157 -0
  149. package/site/models/michigan-plaza-south-geographic.js +84 -0
  150. package/site/models/michigan-plaza-south.js +68 -0
  151. package/site/models/millennium-park-plaza-geographic.js +103 -0
  152. package/site/models/monroe-geographic.js +177 -0
  153. package/site/models/north-michigan-180-geographic.js +117 -0
  154. package/site/models/north-wabash-geographic.js +122 -0
  155. package/site/models/on-the-park-geographic.js +245 -0
  156. package/site/models/one-prudential-geographic.js +55 -0
  157. package/site/models/one-prudential-plaza.js +72 -0
  158. package/site/models/one-prudential-tower.js +246 -0
  159. package/site/models/peoples-gas-geographic.js +250 -0
  160. package/site/models/railway-exchange-geographic.js +176 -0
  161. package/site/models/river-plaza-geographic.js +104 -0
  162. package/site/models/sheraton-grand-geographic.js +112 -0
  163. package/site/models/six-north-michigan-geographic.js +180 -0
  164. package/site/models/skyline-geography-data.js +607 -0
  165. package/site/models/skyline-geography.js +322 -0
  166. package/site/models/skyline-panorama.svg +3491 -0
  167. package/site/models/skyline-reference.svg +3090 -0
  168. package/site/models/swissotel-geographic.js +79 -0
  169. package/site/models/three-illinois-center-geographic.js +86 -0
  170. package/site/models/trump-geographic.js +51 -0
  171. package/site/models/trump-international-tower.js +84 -0
  172. package/site/models/trump-tower.js +229 -0
  173. package/site/models/two-illinois-center-geographic.js +85 -0
  174. package/site/models/two-prudential-geographic.js +47 -0
  175. package/site/models/two-prudential-plaza.js +55 -0
  176. package/site/models/two-prudential-tower.js +441 -0
  177. package/site/models/university-club-geographic.js +217 -0
  178. package/site/models/willoughby-tower-geographic.js +195 -0
  179. package/site/models/window-illumination.js +64 -0
  180. package/site/skyline-3d.html +92 -0
  181. package/site/skyline-3d.js +333 -0
  182. package/site/skyline-animated.svg +18795 -0
  183. package/site/skyline-comparison.js +97 -0
  184. package/site/skyline-original-fit.svg +18312 -0
  185. package/site/skyline-scene.js +86 -0
  186. package/site/skyline-study.html +83 -0
  187. package/site/skyline-study.js +109 -0
  188. package/site/skyline-webgl.html +667 -0
  189. package/site/stars.svg +176 -0
  190. package/site/study-loader.js +20 -0
  191. package/site/study-types.js +1 -0
  192. package/site/study-viewer.js +605 -0
  193. package/site/study.css +204 -0
  194. package/site/vendor/THREE-LICENSE.txt +21 -0
  195. package/site/vendor/three-r186.js +4228 -0
@@ -0,0 +1,322 @@
1
+ import * as THREE from "../vendor/three-r186.js";
2
+ import { line } from "./building-kit.js";
3
+ import { geographicBuildings, geographicStreets } from "./skyline-geography-data.js";
4
+ import { createAonGeographicBuilding } from "./aon-geographic.js";
5
+ import { createBlueCrossGeographicBuilding } from "./blue-cross-geographic.js";
6
+ import { createBorgWarnerGeographicBuilding } from "./borg-warner-geographic.js";
7
+ import { createBuckinghamGeographicBuilding } from "./buckingham-geographic.js";
8
+ import { createChicagoAthleticAssociationGeographicBuilding } from "./chicago-athletic-association-geographic.js";
9
+ import { createCrainGeographicBuilding } from "./crain-geographic.js";
10
+ import { createGageGeographicBuilding } from "./gage-geographic.js";
11
+ import { createHeritageGeographicBuilding } from "./heritage-geographic.js";
12
+ import { createHyattWestTowerGeographicBuilding } from "./hyatt-west-tower-geographic.js";
13
+ import { createAscherGeographicBuilding, createKeithGeographicBuilding } from "./keith-ascher-geographic.js";
14
+ import { createKemperGeographicBuilding } from "./kemper-geographic.js";
15
+ import { createLakeViewGeographicBuilding } from "./lake-view-geographic.js";
16
+ import { createMacleanCenterGeographicBuilding } from "./maclean-center-geographic.js";
17
+ import { createMichiganBoulevardGeographicBuilding } from "./michigan-boulevard-geographic.js";
18
+ import { createMichiganPlazaSouthGeographicBuilding } from "./michigan-plaza-south-geographic.js";
19
+ import { createMillenniumParkPlazaGeographicBuilding } from "./millennium-park-plaza-geographic.js";
20
+ import { createMonroeGeographicBuilding } from "./monroe-geographic.js";
21
+ import { createNorthMichigan180GeographicBuilding } from "./north-michigan-180-geographic.js";
22
+ import { createNorthWabashGeographicBuilding } from "./north-wabash-geographic.js";
23
+ import { createOnTheParkGeographicBuilding } from "./on-the-park-geographic.js";
24
+ import { createOnePrudentialGeographicBuilding } from "./one-prudential-geographic.js";
25
+ import { createPeoplesGasGeographicBuilding } from "./peoples-gas-geographic.js";
26
+ import { createRailwayExchangeGeographicBuilding } from "./railway-exchange-geographic.js";
27
+ import { createRiverPlazaGeographicBuilding } from "./river-plaza-geographic.js";
28
+ import { createSheratonGrandGeographicBuilding } from "./sheraton-grand-geographic.js";
29
+ import { createSixNorthMichiganGeographicBuilding } from "./six-north-michigan-geographic.js";
30
+ import { createSwissotelGeographicBuilding } from "./swissotel-geographic.js";
31
+ import { createThreeIllinoisCenterGeographicBuilding } from "./three-illinois-center-geographic.js";
32
+ import { createTrumpGeographicBuilding } from "./trump-geographic.js";
33
+ import { createTwoIllinoisCenterGeographicBuilding } from "./two-illinois-center-geographic.js";
34
+ import { createTwoPrudentialGeographicBuilding } from "./two-prudential-geographic.js";
35
+ import { createUniversityClubGeographicBuilding } from "./university-club-geographic.js";
36
+ import { createWilloughbyTowerGeographicBuilding } from "./willoughby-tower-geographic.js";
37
+ // Local WGS84 tangent plane, centered on the bounding-box center of Crain's
38
+ // mapped footprint. Ground coordinates are east/north meters; Three uses x/-z.
39
+ export const geographicOrigin = [-87.62497155, 41.88482645];
40
+ const radians = Math.PI / 180;
41
+ const latitude = geographicOrigin[1] * radians;
42
+ const eccentricitySquared = 6.69437999014e-3;
43
+ const w = Math.sqrt(1 - eccentricitySquared * Math.sin(latitude) ** 2);
44
+ const eastPerDegree = 6378137 / w * Math.cos(latitude) * radians;
45
+ const northPerDegree = 6378137 * (1 - eccentricitySquared) / w ** 3 * radians;
46
+ export function projectGround([longitude, lat]) {
47
+ return [(longitude - geographicOrigin[0]) * eastPerDegree, (lat - geographicOrigin[1]) * northPerDegree];
48
+ }
49
+ export function footprintMetrics(coordinates) {
50
+ const points = coordinates.map(projectGround);
51
+ const min = [0, 1].map((axis) => Math.min(...points.map((p) => p[axis])));
52
+ const max = [0, 1].map((axis) => Math.max(...points.map((p) => p[axis])));
53
+ const area = Math.abs(points.reduce((sum, a, i) => {
54
+ const b = points[(i + 1) % points.length];
55
+ return sum + a[0] * b[1] - b[0] * a[1];
56
+ }, 0)) / 2;
57
+ return { min, max, center: min.map((n, i) => (n + max[i]) / 2), size: min.map((n, i) => max[i] - n), area };
58
+ }
59
+ function plan(coordinates) {
60
+ const points = coordinates.map((p) => { const [east, north] = projectGround(p); return [east, -north]; });
61
+ const area = points.reduce((sum, a, i) => {
62
+ const b = points[(i + 1) % points.length];
63
+ return sum + a[0] * b[1] - b[0] * a[1];
64
+ }, 0);
65
+ if (area > 0)
66
+ points.reverse();
67
+ return points.map((p, i) => line(p, points[(i + 1) % points.length]));
68
+ }
69
+ export function createGeographicBuilding(record, offset = [0, 0]) {
70
+ if (record.id === "layer3") {
71
+ return createAonGeographicBuilding(record, plan, offset);
72
+ }
73
+ if (record.id === "building-crain-communications") {
74
+ return createCrainGeographicBuilding(record, plan, offset);
75
+ }
76
+ if (record.id === "building-blue-cross-blue-shield") {
77
+ return createBlueCrossGeographicBuilding(record, plan, offset);
78
+ }
79
+ if (record.id === "building-340-on-the-park") {
80
+ return createOnTheParkGeographicBuilding(record, plan, offset);
81
+ }
82
+ if (record.id === "building-the-buckingham") {
83
+ return createBuckinghamGeographicBuilding(record, plan, offset);
84
+ }
85
+ if (record.id === "building-michigan-plaza-front-tall") {
86
+ return createMillenniumParkPlazaGeographicBuilding(record, plan, offset);
87
+ }
88
+ if (record.id === "building-willoughby-tower") {
89
+ return createWilloughbyTowerGeographicBuilding(record, plan, offset);
90
+ }
91
+ if (record.id === "building-six-north-michigan") {
92
+ return createSixNorthMichiganGeographicBuilding(record, plan, offset);
93
+ }
94
+ if (record.id === "building-six-north-far-east") {
95
+ return createMichiganBoulevardGeographicBuilding(record, plan, offset);
96
+ }
97
+ if (record.id === "building-michigan-plaza-front-middle") {
98
+ return createNorthMichigan180GeographicBuilding(record, plan, offset);
99
+ }
100
+ if (record.id === "building-university-club") {
101
+ return createUniversityClubGeographicBuilding(record, plan, offset);
102
+ }
103
+ if (record.id === "building-monroe") {
104
+ return createMonroeGeographicBuilding(record, plan, offset);
105
+ }
106
+ if (record.id === "building-maclean-center") {
107
+ return createMacleanCenterGeographicBuilding(record, plan, offset);
108
+ }
109
+ if (record.id === "building-lakeview") {
110
+ return createLakeViewGeographicBuilding(record, plan, offset);
111
+ }
112
+ if (record.id === "building-peoples-gas") {
113
+ return createPeoplesGasGeographicBuilding(record, plan, offset);
114
+ }
115
+ if (record.id === "building-200-south-michigan") {
116
+ return createBorgWarnerGeographicBuilding(record, plan, offset);
117
+ }
118
+ if (record.id === "building-railway-exchange") {
119
+ return createRailwayExchangeGeographicBuilding(record, plan, offset);
120
+ }
121
+ if (record.id === "building-michigan-west-right") {
122
+ return createGageGeographicBuilding(record, plan, offset);
123
+ }
124
+ if (record.id === "building-michigan-west-front") {
125
+ return createKeithGeographicBuilding(record, plan, offset);
126
+ }
127
+ if (record.id === "building-30-south-michigan") {
128
+ return createAscherGeographicBuilding(record, plan, offset);
129
+ }
130
+ if (record.id === "building-chicago-athletic-association") {
131
+ return createChicagoAthleticAssociationGeographicBuilding(record, plan, offset);
132
+ }
133
+ if (record.id === "building-office-west-of-aon") {
134
+ return createTwoIllinoisCenterGeographicBuilding(record, plan, offset);
135
+ }
136
+ if (record.id === "building-river-plaza") {
137
+ return createRiverPlazaGeographicBuilding(record, plan, offset);
138
+ }
139
+ if (record.id === "building-hyatt-regency-west-tower") {
140
+ return createHyattWestTowerGeographicBuilding(record, plan, offset);
141
+ }
142
+ if (record.id === "building-buckingham-east") {
143
+ return createSheratonGrandGeographicBuilding(record, plan, offset);
144
+ }
145
+ if (record.id === "building-three-illinois-center") {
146
+ return createThreeIllinoisCenterGeographicBuilding(record, plan, offset);
147
+ }
148
+ if (record.id === "building-swissotel") {
149
+ return createSwissotelGeographicBuilding(record, plan, offset);
150
+ }
151
+ if (record.id === "building-heritage-at-millennium-park") {
152
+ return createHeritageGeographicBuilding(record, plan, offset);
153
+ }
154
+ if (record.id === "building-kemper") {
155
+ return createKemperGeographicBuilding(record, plan, offset);
156
+ }
157
+ if (record.id === "building-michigan-plaza-south-tower") {
158
+ return createMichiganPlazaSouthGeographicBuilding(record, plan, offset);
159
+ }
160
+ if (record.id === "building-330-north-wabash") {
161
+ return createNorthWabashGeographicBuilding(record, plan, offset);
162
+ }
163
+ if (record.id === "building-one-prudential-plaza") {
164
+ return createOnePrudentialGeographicBuilding(record, plan, offset);
165
+ }
166
+ if (record.id === "building-trump-tower-only") {
167
+ return createTrumpGeographicBuilding(record, plan, offset);
168
+ }
169
+ if (record.id === "building-two-prudential-plaza") {
170
+ return createTwoPrudentialGeographicBuilding(record, plan, offset);
171
+ }
172
+ // Every geographic building has a dedicated factory; a record without one
173
+ // fails loudly here rather than silently rendering generic massing.
174
+ throw new Error(`No geographic factory for ${record.id}.`);
175
+ }
176
+ function lines(points, color) {
177
+ const geometry = new THREE.BufferGeometry();
178
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(points.flat(), 3));
179
+ return new THREE.LineSegments(geometry, new THREE.LineBasicMaterial({ color }));
180
+ }
181
+ // A street's roadway, 3.3 m to a lane: its mapped lanes, or its class's usual count, which
182
+ // depends on whether it is one-way. A named service way is a single 5 m lane.
183
+ const usualLanes = {
184
+ trunk: [3, 6], primary: [3, 4], secondary: [3, 4], tertiary: [2, 2], residential: [1, 2], unclassified: [1, 2], living_street: [1, 2],
185
+ };
186
+ export function streetWidth(street) {
187
+ if (street.highway === "service")
188
+ return 5;
189
+ const lanes = street.lanes ?? (street.highway.endsWith("_link") ? 1 : usualLanes[street.highway]?.[street.oneway ? 0 : 1] ?? 2);
190
+ return lanes * 3.3;
191
+ }
192
+ // Clips a polygon in the ground's x/z plane to a rectangle (Sutherland–Hodgman).
193
+ function clipToRectangle(polygon, [xMin, xMax], [zMin, zMax]) {
194
+ const edges = [[0, xMin, false], [0, xMax, true], [1, zMin, false], [1, zMax, true]];
195
+ return edges.reduce((points, [axis, limit, keepBelow]) => {
196
+ const inside = (p) => keepBelow ? p[axis] <= limit : p[axis] >= limit;
197
+ const clipped = [];
198
+ points.forEach((current, i) => {
199
+ const previous = points[(i + points.length - 1) % points.length];
200
+ if (inside(current) !== inside(previous)) {
201
+ const t = (limit - previous[axis]) / (current[axis] - previous[axis]);
202
+ clipped.push([previous[0] + (current[0] - previous[0]) * t, previous[1] + (current[1] - previous[1]) * t]);
203
+ }
204
+ if (inside(current))
205
+ clipped.push(current);
206
+ });
207
+ return clipped;
208
+ }, polygon);
209
+ }
210
+ // The mapped streets as roadway surfaces, each its width about its centreline with round
211
+ // joins, cut to a rectangle of the ground (x east, z south, in metres from the ground's
212
+ // origin) so no road runs past the platform it lies on. They sit just above the platform,
213
+ // under the footprint outlines; overlapping roadways share one flat colour. Marked as
214
+ // ground, so they take building shadows wherever the platform does.
215
+ export function createGeographicRoads(bounds, color, offset = [0, 0]) {
216
+ const positions = [];
217
+ const addPolygon = (polygon) => {
218
+ const points = clipToRectangle(polygon, bounds.x, bounds.z);
219
+ for (let i = 1; i < points.length - 1; i += 1) {
220
+ const a = points[0], b = points[i], c = points[i + 1];
221
+ // Wind each triangle to face up, and drop the slivers a clip or a straight run leaves.
222
+ const area = (b[1] - a[1]) * (c[0] - a[0]) - (b[0] - a[0]) * (c[1] - a[1]);
223
+ if (Math.abs(area) < 1e-6)
224
+ continue;
225
+ for (const [x, z] of area > 0 ? [a, b, c] : [a, c, b])
226
+ positions.push(x, 0.03, z);
227
+ }
228
+ };
229
+ for (const street of geographicStreets) {
230
+ const radius = streetWidth(street) / 2;
231
+ const points = street.coordinates.map((coordinate) => { const [east, north] = projectGround(coordinate); return [east, -north]; })
232
+ .filter((p, i, all) => i === 0 || Math.hypot(p[0] - all[i - 1][0], p[1] - all[i - 1][1]) > 1e-6);
233
+ // Each segment's unit normal, scaled to the roadway's half width.
234
+ const sides = points.slice(1).map((b, i) => {
235
+ const a = points[i], length = Math.hypot(b[0] - a[0], b[1] - a[1]);
236
+ return [-(b[1] - a[1]) / length * radius, (b[0] - a[0]) / length * radius];
237
+ });
238
+ points.forEach((a, i) => {
239
+ // A round join where the street ends or turns. A shallow bend needs only the bevel
240
+ // between its segments' edge corners, on either side, to close the outer wedge.
241
+ const before = sides[i - 1], after = sides[i];
242
+ const turn = before && after ? Math.abs(Math.atan2(before[0] * after[1] - before[1] * after[0], before[0] * after[0] + before[1] * after[1])) : Math.PI;
243
+ if (turn > 0.05) {
244
+ // Twelve sides about the half width's circle, so every edge corner within it is covered.
245
+ const joint = [], reach = radius / Math.cos(Math.PI / 12);
246
+ for (let k = 0; k < 12; k += 1)
247
+ joint.push([a[0] + reach * Math.cos(k * Math.PI / 6), a[1] + reach * Math.sin(k * Math.PI / 6)]);
248
+ addPolygon(joint);
249
+ }
250
+ else if (before && after) {
251
+ for (const sign of [1, -1])
252
+ addPolygon([a, [a[0] + sign * before[0], a[1] + sign * before[1]], [a[0] + sign * after[0], a[1] + sign * after[1]]]);
253
+ }
254
+ const b = points[i + 1];
255
+ if (!b || !after)
256
+ return;
257
+ addPolygon([[a[0] + after[0], a[1] + after[1]], [b[0] + after[0], b[1] + after[1]], [b[0] - after[0], b[1] - after[1]], [a[0] - after[0], a[1] - after[1]]]);
258
+ });
259
+ }
260
+ const geometry = new THREE.BufferGeometry();
261
+ geometry.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
262
+ geometry.setAttribute("normal", new THREE.Float32BufferAttribute(positions.map((_, i) => i % 3 === 1 ? 1 : 0), 3));
263
+ const roads = new THREE.Mesh(geometry, new THREE.MeshToonMaterial({ color }));
264
+ roads.name = "Mapped streets";
265
+ roads.position.set(offset[0], 0, offset[1]);
266
+ roads.receiveShadow = true;
267
+ roads.userData["ground"] = true;
268
+ return roads;
269
+ }
270
+ export function createGeographicGround(offset = [0, 0]) {
271
+ const group = new THREE.Group();
272
+ group.name = "Geographic reference";
273
+ group.position.set(offset[0], 0, offset[1]);
274
+ // The study rectangle, 100 m squares from 400 m west of Crain to 500 m east and from
275
+ // 850 m south, past the Railway Exchange at Jackson, to 650 m north.
276
+ const grid = [];
277
+ for (let x = -400; x <= 500; x += 100)
278
+ grid.push([x, 0.02, -650], [x, 0.02, 850]);
279
+ for (let north = -850; north <= 650; north += 100)
280
+ grid.push([-400, 0.02, -north], [500, 0.02, -north]);
281
+ group.add(lines(grid, 0x393939));
282
+ const streets = new THREE.Group();
283
+ streets.name = "Mapped street centerlines";
284
+ for (const street of geographicStreets) {
285
+ const points = street.coordinates.map((coordinate) => {
286
+ const [east, north] = projectGround(coordinate);
287
+ return [east, 0.06, -north];
288
+ });
289
+ const segments = [];
290
+ for (let i = 1; i < points.length; i += 1) {
291
+ // Clip every segment to the study rectangle, retaining crossing segments.
292
+ const a = points[i - 1], b = points[i];
293
+ let lo = 0, hi = 1;
294
+ for (const [axis, min, max] of [[0, -400, 500], [2, -650, 850]]) {
295
+ const delta = b[axis] - a[axis];
296
+ if (Math.abs(delta) < 1e-10) {
297
+ if (a[axis] < min || a[axis] > max)
298
+ hi = -1;
299
+ }
300
+ else {
301
+ const t1 = (min - a[axis]) / delta, t2 = (max - a[axis]) / delta;
302
+ lo = Math.max(lo, Math.min(t1, t2));
303
+ hi = Math.min(hi, Math.max(t1, t2));
304
+ }
305
+ }
306
+ if (lo <= hi)
307
+ segments.push(...[lo, hi].map((t) => a.map((n, axis) => n + (b[axis] - n) * t)));
308
+ }
309
+ const object = lines(segments, 0xa7a7a7);
310
+ object.name = street.name;
311
+ streets.add(object);
312
+ }
313
+ group.add(streets);
314
+ const outlines = [];
315
+ for (const record of geographicBuildings) {
316
+ const points = record.footprint.coordinates.map((p) => { const [x, y] = projectGround(p); return [x, 0.1, -y]; });
317
+ points.forEach((p, i) => outlines.push(p, points[(i + 1) % points.length]));
318
+ }
319
+ const footprints = lines(outlines, 0xc9c9c9);
320
+ group.add(footprints);
321
+ return { group, streets, footprints };
322
+ }