RectangleOutlineGeometry.js 17 KB

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