RectangleOutlineGeometry.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539
  1. import arrayFill from "./arrayFill.js";
  2. import BoundingSphere from "./BoundingSphere.js";
  3. import Cartesian3 from "./Cartesian3.js";
  4. import Cartographic from "./Cartographic.js";
  5. import ComponentDatatype from "./ComponentDatatype.js";
  6. import defaultValue from "./defaultValue.js";
  7. import defined from "./defined.js";
  8. import DeveloperError from "./DeveloperError.js";
  9. import Ellipsoid from "./Ellipsoid.js";
  10. import Geometry from "./Geometry.js";
  11. import GeometryAttribute from "./GeometryAttribute.js";
  12. import GeometryAttributes from "./GeometryAttributes.js";
  13. import GeometryOffsetAttribute from "./GeometryOffsetAttribute.js";
  14. import IndexDatatype from "./IndexDatatype.js";
  15. import CesiumMath from "./Math.js";
  16. import PolygonPipeline from "./PolygonPipeline.js";
  17. import PrimitiveType from "./PrimitiveType.js";
  18. import Rectangle from "./Rectangle.js";
  19. import RectangleGeometryLibrary from "./RectangleGeometryLibrary.js";
  20. const bottomBoundingSphere = new BoundingSphere();
  21. const topBoundingSphere = new BoundingSphere();
  22. const positionScratch = new Cartesian3();
  23. const rectangleScratch = new Rectangle();
  24. function constructRectangle(geometry, computedOptions) {
  25. const ellipsoid = geometry._ellipsoid;
  26. const height = computedOptions.height;
  27. const width = computedOptions.width;
  28. const northCap = computedOptions.northCap;
  29. const southCap = computedOptions.southCap;
  30. let rowHeight = height;
  31. let widthMultiplier = 2;
  32. let size = 0;
  33. let corners = 4;
  34. if (northCap) {
  35. widthMultiplier -= 1;
  36. rowHeight -= 1;
  37. size += 1;
  38. corners -= 2;
  39. }
  40. if (southCap) {
  41. widthMultiplier -= 1;
  42. rowHeight -= 1;
  43. size += 1;
  44. corners -= 2;
  45. }
  46. size += widthMultiplier * width + 2 * rowHeight - corners;
  47. const positions = new Float64Array(size * 3);
  48. let posIndex = 0;
  49. let row = 0;
  50. let col;
  51. const position = positionScratch;
  52. if (northCap) {
  53. RectangleGeometryLibrary.computePosition(
  54. computedOptions,
  55. ellipsoid,
  56. false,
  57. row,
  58. 0,
  59. position
  60. );
  61. positions[posIndex++] = position.x;
  62. positions[posIndex++] = position.y;
  63. positions[posIndex++] = position.z;
  64. } else {
  65. for (col = 0; col < width; col++) {
  66. RectangleGeometryLibrary.computePosition(
  67. computedOptions,
  68. ellipsoid,
  69. false,
  70. row,
  71. col,
  72. position
  73. );
  74. positions[posIndex++] = position.x;
  75. positions[posIndex++] = position.y;
  76. positions[posIndex++] = position.z;
  77. }
  78. }
  79. col = width - 1;
  80. for (row = 1; row < height; row++) {
  81. RectangleGeometryLibrary.computePosition(
  82. computedOptions,
  83. ellipsoid,
  84. false,
  85. row,
  86. col,
  87. position
  88. );
  89. positions[posIndex++] = position.x;
  90. positions[posIndex++] = position.y;
  91. positions[posIndex++] = position.z;
  92. }
  93. row = height - 1;
  94. if (!southCap) {
  95. // if southCap is true, we dont need to add any more points because the south pole point was added by the iteration above
  96. for (col = width - 2; col >= 0; col--) {
  97. RectangleGeometryLibrary.computePosition(
  98. computedOptions,
  99. ellipsoid,
  100. false,
  101. row,
  102. col,
  103. position
  104. );
  105. positions[posIndex++] = position.x;
  106. positions[posIndex++] = position.y;
  107. positions[posIndex++] = position.z;
  108. }
  109. }
  110. col = 0;
  111. for (row = height - 2; row > 0; row--) {
  112. RectangleGeometryLibrary.computePosition(
  113. computedOptions,
  114. ellipsoid,
  115. false,
  116. row,
  117. col,
  118. position
  119. );
  120. positions[posIndex++] = position.x;
  121. positions[posIndex++] = position.y;
  122. positions[posIndex++] = position.z;
  123. }
  124. const indicesSize = (positions.length / 3) * 2;
  125. const indices = IndexDatatype.createTypedArray(
  126. positions.length / 3,
  127. indicesSize
  128. );
  129. let index = 0;
  130. for (let i = 0; i < positions.length / 3 - 1; i++) {
  131. indices[index++] = i;
  132. indices[index++] = i + 1;
  133. }
  134. indices[index++] = positions.length / 3 - 1;
  135. indices[index++] = 0;
  136. const geo = new Geometry({
  137. attributes: new GeometryAttributes(),
  138. primitiveType: PrimitiveType.LINES,
  139. });
  140. geo.attributes.position = new GeometryAttribute({
  141. componentDatatype: ComponentDatatype.DOUBLE,
  142. componentsPerAttribute: 3,
  143. values: positions,
  144. });
  145. geo.indices = indices;
  146. return geo;
  147. }
  148. function constructExtrudedRectangle(rectangleGeometry, computedOptions) {
  149. const surfaceHeight = rectangleGeometry._surfaceHeight;
  150. const extrudedHeight = rectangleGeometry._extrudedHeight;
  151. const ellipsoid = rectangleGeometry._ellipsoid;
  152. const minHeight = extrudedHeight;
  153. const maxHeight = surfaceHeight;
  154. const geo = constructRectangle(rectangleGeometry, computedOptions);
  155. const height = computedOptions.height;
  156. const width = computedOptions.width;
  157. const topPositions = PolygonPipeline.scaleToGeodeticHeight(
  158. geo.attributes.position.values,
  159. maxHeight,
  160. ellipsoid,
  161. false
  162. );
  163. let length = topPositions.length;
  164. const positions = new Float64Array(length * 2);
  165. positions.set(topPositions);
  166. const bottomPositions = PolygonPipeline.scaleToGeodeticHeight(
  167. geo.attributes.position.values,
  168. minHeight,
  169. ellipsoid
  170. );
  171. positions.set(bottomPositions, length);
  172. geo.attributes.position.values = positions;
  173. const northCap = computedOptions.northCap;
  174. const southCap = computedOptions.southCap;
  175. let corners = 4;
  176. if (northCap) {
  177. corners -= 1;
  178. }
  179. if (southCap) {
  180. corners -= 1;
  181. }
  182. const indicesSize = (positions.length / 3 + corners) * 2;
  183. const indices = IndexDatatype.createTypedArray(
  184. positions.length / 3,
  185. indicesSize
  186. );
  187. length = positions.length / 6;
  188. let index = 0;
  189. for (let i = 0; i < length - 1; i++) {
  190. indices[index++] = i;
  191. indices[index++] = i + 1;
  192. indices[index++] = i + length;
  193. indices[index++] = i + length + 1;
  194. }
  195. indices[index++] = length - 1;
  196. indices[index++] = 0;
  197. indices[index++] = length + length - 1;
  198. indices[index++] = length;
  199. indices[index++] = 0;
  200. indices[index++] = length;
  201. let bottomCorner;
  202. if (northCap) {
  203. bottomCorner = height - 1;
  204. } else {
  205. const topRightCorner = width - 1;
  206. indices[index++] = topRightCorner;
  207. indices[index++] = topRightCorner + length;
  208. bottomCorner = width + height - 2;
  209. }
  210. indices[index++] = bottomCorner;
  211. indices[index++] = bottomCorner + length;
  212. if (!southCap) {
  213. const bottomLeftCorner = width + bottomCorner - 1;
  214. indices[index++] = bottomLeftCorner;
  215. indices[index] = bottomLeftCorner + length;
  216. }
  217. geo.indices = indices;
  218. return geo;
  219. }
  220. /**
  221. * A description of the outline of a a cartographic rectangle on an ellipsoid centered at the origin.
  222. *
  223. * @alias RectangleOutlineGeometry
  224. * @constructor
  225. *
  226. * @param {Object} options Object with the following properties:
  227. * @param {Rectangle} options.rectangle A cartographic rectangle with north, south, east and west properties in radians.
  228. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid on which the rectangle lies.
  229. * @param {Number} [options.granularity=CesiumMath.RADIANS_PER_DEGREE] The distance, in radians, between each latitude and longitude. Determines the number of positions in the buffer.
  230. * @param {Number} [options.height=0.0] The distance in meters between the rectangle and the ellipsoid surface.
  231. * @param {Number} [options.rotation=0.0] The rotation of the rectangle, in radians. A positive rotation is counter-clockwise.
  232. * @param {Number} [options.extrudedHeight] The distance in meters between the rectangle's extruded face and the ellipsoid surface.
  233. *
  234. * @exception {DeveloperError} <code>options.rectangle.north</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
  235. * @exception {DeveloperError} <code>options.rectangle.south</code> must be in the interval [<code>-Pi/2</code>, <code>Pi/2</code>].
  236. * @exception {DeveloperError} <code>options.rectangle.east</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
  237. * @exception {DeveloperError} <code>options.rectangle.west</code> must be in the interval [<code>-Pi</code>, <code>Pi</code>].
  238. * @exception {DeveloperError} <code>options.rectangle.north</code> must be greater than <code>rectangle.south</code>.
  239. *
  240. * @see RectangleOutlineGeometry#createGeometry
  241. *
  242. * @example
  243. * const rectangle = new Cesium.RectangleOutlineGeometry({
  244. * ellipsoid : Cesium.Ellipsoid.WGS84,
  245. * rectangle : Cesium.Rectangle.fromDegrees(-80.0, 39.0, -74.0, 42.0),
  246. * height : 10000.0
  247. * });
  248. * const geometry = Cesium.RectangleOutlineGeometry.createGeometry(rectangle);
  249. */
  250. function RectangleOutlineGeometry(options) {
  251. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  252. const rectangle = options.rectangle;
  253. const granularity = defaultValue(
  254. options.granularity,
  255. CesiumMath.RADIANS_PER_DEGREE
  256. );
  257. const ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  258. const rotation = defaultValue(options.rotation, 0.0);
  259. //>>includeStart('debug', pragmas.debug);
  260. if (!defined(rectangle)) {
  261. throw new DeveloperError("rectangle is required.");
  262. }
  263. Rectangle.validate(rectangle);
  264. if (rectangle.north < rectangle.south) {
  265. throw new DeveloperError(
  266. "options.rectangle.north must be greater than options.rectangle.south"
  267. );
  268. }
  269. //>>includeEnd('debug');
  270. const height = defaultValue(options.height, 0.0);
  271. const extrudedHeight = defaultValue(options.extrudedHeight, height);
  272. this._rectangle = Rectangle.clone(rectangle);
  273. this._granularity = granularity;
  274. this._ellipsoid = ellipsoid;
  275. this._surfaceHeight = Math.max(height, extrudedHeight);
  276. this._rotation = rotation;
  277. this._extrudedHeight = Math.min(height, extrudedHeight);
  278. this._offsetAttribute = options.offsetAttribute;
  279. this._workerName = "createRectangleOutlineGeometry";
  280. }
  281. /**
  282. * The number of elements used to pack the object into an array.
  283. * @type {Number}
  284. */
  285. RectangleOutlineGeometry.packedLength =
  286. Rectangle.packedLength + Ellipsoid.packedLength + 5;
  287. /**
  288. * Stores the provided instance into the provided array.
  289. *
  290. * @param {RectangleOutlineGeometry} value The value to pack.
  291. * @param {Number[]} array The array to pack into.
  292. * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
  293. *
  294. * @returns {Number[]} The array that was packed into
  295. */
  296. RectangleOutlineGeometry.pack = function (value, array, startingIndex) {
  297. //>>includeStart('debug', pragmas.debug);
  298. if (!defined(value)) {
  299. throw new DeveloperError("value is required");
  300. }
  301. if (!defined(array)) {
  302. throw new DeveloperError("array is required");
  303. }
  304. //>>includeEnd('debug');
  305. startingIndex = defaultValue(startingIndex, 0);
  306. Rectangle.pack(value._rectangle, array, startingIndex);
  307. startingIndex += Rectangle.packedLength;
  308. Ellipsoid.pack(value._ellipsoid, array, startingIndex);
  309. startingIndex += Ellipsoid.packedLength;
  310. array[startingIndex++] = value._granularity;
  311. array[startingIndex++] = value._surfaceHeight;
  312. array[startingIndex++] = value._rotation;
  313. array[startingIndex++] = value._extrudedHeight;
  314. array[startingIndex] = defaultValue(value._offsetAttribute, -1);
  315. return array;
  316. };
  317. const scratchRectangle = new Rectangle();
  318. const scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
  319. const scratchOptions = {
  320. rectangle: scratchRectangle,
  321. ellipsoid: scratchEllipsoid,
  322. granularity: undefined,
  323. height: undefined,
  324. rotation: undefined,
  325. extrudedHeight: undefined,
  326. offsetAttribute: undefined,
  327. };
  328. /**
  329. * Retrieves an instance from a packed array.
  330. *
  331. * @param {Number[]} array The packed array.
  332. * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
  333. * @param {RectangleOutlineGeometry} [result] The object into which to store the result.
  334. * @returns {RectangleOutlineGeometry} The modified result parameter or a new Quaternion instance if one was not provided.
  335. */
  336. RectangleOutlineGeometry.unpack = function (array, startingIndex, result) {
  337. //>>includeStart('debug', pragmas.debug);
  338. if (!defined(array)) {
  339. throw new DeveloperError("array is required");
  340. }
  341. //>>includeEnd('debug');
  342. startingIndex = defaultValue(startingIndex, 0);
  343. const rectangle = Rectangle.unpack(array, startingIndex, scratchRectangle);
  344. startingIndex += Rectangle.packedLength;
  345. const ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
  346. startingIndex += Ellipsoid.packedLength;
  347. const granularity = array[startingIndex++];
  348. const height = array[startingIndex++];
  349. const rotation = array[startingIndex++];
  350. const extrudedHeight = array[startingIndex++];
  351. const offsetAttribute = array[startingIndex];
  352. if (!defined(result)) {
  353. scratchOptions.granularity = granularity;
  354. scratchOptions.height = height;
  355. scratchOptions.rotation = rotation;
  356. scratchOptions.extrudedHeight = extrudedHeight;
  357. scratchOptions.offsetAttribute =
  358. offsetAttribute === -1 ? undefined : offsetAttribute;
  359. return new RectangleOutlineGeometry(scratchOptions);
  360. }
  361. result._rectangle = Rectangle.clone(rectangle, result._rectangle);
  362. result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
  363. result._surfaceHeight = height;
  364. result._rotation = rotation;
  365. result._extrudedHeight = extrudedHeight;
  366. result._offsetAttribute =
  367. offsetAttribute === -1 ? undefined : offsetAttribute;
  368. return result;
  369. };
  370. const nwScratch = new Cartographic();
  371. /**
  372. * Computes the geometric representation of an outline of a rectangle, including its vertices, indices, and a bounding sphere.
  373. *
  374. * @param {RectangleOutlineGeometry} rectangleGeometry A description of the rectangle outline.
  375. * @returns {Geometry|undefined} The computed vertices and indices.
  376. *
  377. * @exception {DeveloperError} Rotated rectangle is invalid.
  378. */
  379. RectangleOutlineGeometry.createGeometry = function (rectangleGeometry) {
  380. const rectangle = rectangleGeometry._rectangle;
  381. const ellipsoid = rectangleGeometry._ellipsoid;
  382. const computedOptions = RectangleGeometryLibrary.computeOptions(
  383. rectangle,
  384. rectangleGeometry._granularity,
  385. rectangleGeometry._rotation,
  386. 0,
  387. rectangleScratch,
  388. nwScratch
  389. );
  390. let geometry;
  391. let boundingSphere;
  392. if (
  393. CesiumMath.equalsEpsilon(
  394. rectangle.north,
  395. rectangle.south,
  396. CesiumMath.EPSILON10
  397. ) ||
  398. CesiumMath.equalsEpsilon(
  399. rectangle.east,
  400. rectangle.west,
  401. CesiumMath.EPSILON10
  402. )
  403. ) {
  404. return undefined;
  405. }
  406. const surfaceHeight = rectangleGeometry._surfaceHeight;
  407. const extrudedHeight = rectangleGeometry._extrudedHeight;
  408. const extrude = !CesiumMath.equalsEpsilon(
  409. surfaceHeight,
  410. extrudedHeight,
  411. 0,
  412. CesiumMath.EPSILON2
  413. );
  414. let offsetValue;
  415. if (extrude) {
  416. geometry = constructExtrudedRectangle(rectangleGeometry, computedOptions);
  417. if (defined(rectangleGeometry._offsetAttribute)) {
  418. const size = geometry.attributes.position.values.length / 3;
  419. let offsetAttribute = new Uint8Array(size);
  420. if (rectangleGeometry._offsetAttribute === GeometryOffsetAttribute.TOP) {
  421. offsetAttribute = arrayFill(offsetAttribute, 1, 0, size / 2);
  422. } else {
  423. offsetValue =
  424. rectangleGeometry._offsetAttribute === GeometryOffsetAttribute.NONE
  425. ? 0
  426. : 1;
  427. offsetAttribute = arrayFill(offsetAttribute, offsetValue);
  428. }
  429. geometry.attributes.applyOffset = new GeometryAttribute({
  430. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  431. componentsPerAttribute: 1,
  432. values: offsetAttribute,
  433. });
  434. }
  435. const topBS = BoundingSphere.fromRectangle3D(
  436. rectangle,
  437. ellipsoid,
  438. surfaceHeight,
  439. topBoundingSphere
  440. );
  441. const bottomBS = BoundingSphere.fromRectangle3D(
  442. rectangle,
  443. ellipsoid,
  444. extrudedHeight,
  445. bottomBoundingSphere
  446. );
  447. boundingSphere = BoundingSphere.union(topBS, bottomBS);
  448. } else {
  449. geometry = constructRectangle(rectangleGeometry, computedOptions);
  450. geometry.attributes.position.values = PolygonPipeline.scaleToGeodeticHeight(
  451. geometry.attributes.position.values,
  452. surfaceHeight,
  453. ellipsoid,
  454. false
  455. );
  456. if (defined(rectangleGeometry._offsetAttribute)) {
  457. const length = geometry.attributes.position.values.length;
  458. const applyOffset = new Uint8Array(length / 3);
  459. offsetValue =
  460. rectangleGeometry._offsetAttribute === GeometryOffsetAttribute.NONE
  461. ? 0
  462. : 1;
  463. arrayFill(applyOffset, offsetValue);
  464. geometry.attributes.applyOffset = new GeometryAttribute({
  465. componentDatatype: ComponentDatatype.UNSIGNED_BYTE,
  466. componentsPerAttribute: 1,
  467. values: applyOffset,
  468. });
  469. }
  470. boundingSphere = BoundingSphere.fromRectangle3D(
  471. rectangle,
  472. ellipsoid,
  473. surfaceHeight
  474. );
  475. }
  476. return new Geometry({
  477. attributes: geometry.attributes,
  478. indices: geometry.indices,
  479. primitiveType: PrimitiveType.LINES,
  480. boundingSphere: boundingSphere,
  481. offsetAttribute: rectangleGeometry._offsetAttribute,
  482. });
  483. };
  484. export default RectangleOutlineGeometry;