WallGeometry.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551
  1. import BoundingSphere from './BoundingSphere.js';
  2. import Cartesian3 from './Cartesian3.js';
  3. import ComponentDatatype from './ComponentDatatype.js';
  4. import defaultValue from './defaultValue.js';
  5. import defined from './defined.js';
  6. import DeveloperError from './DeveloperError.js';
  7. import Ellipsoid from './Ellipsoid.js';
  8. import Geometry from './Geometry.js';
  9. import GeometryAttribute from './GeometryAttribute.js';
  10. import GeometryAttributes from './GeometryAttributes.js';
  11. import IndexDatatype from './IndexDatatype.js';
  12. import CesiumMath from './Math.js';
  13. import PrimitiveType from './PrimitiveType.js';
  14. import VertexFormat from './VertexFormat.js';
  15. import WallGeometryLibrary from './WallGeometryLibrary.js';
  16. var scratchCartesian3Position1 = new Cartesian3();
  17. var scratchCartesian3Position2 = new Cartesian3();
  18. var scratchCartesian3Position3 = new Cartesian3();
  19. var scratchCartesian3Position4 = new Cartesian3();
  20. var scratchCartesian3Position5 = new Cartesian3();
  21. var scratchBitangent = new Cartesian3();
  22. var scratchTangent = new Cartesian3();
  23. var scratchNormal = new Cartesian3();
  24. /**
  25. * A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
  26. * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
  27. *
  28. * @alias WallGeometry
  29. * @constructor
  30. *
  31. * @param {Object} options Object with the following properties:
  32. * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
  33. * @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.
  34. * @param {Number[]} [options.maximumHeights] An array parallel to <code>positions</code> that give the maximum height of the
  35. * wall at <code>positions</code>. If undefined, the height of each position in used.
  36. * @param {Number[]} [options.minimumHeights] An array parallel to <code>positions</code> that give the minimum height of the
  37. * wall at <code>positions</code>. If undefined, the height at each position is 0.0.
  38. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
  39. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
  40. *
  41. * @exception {DeveloperError} positions length must be greater than or equal to 2.
  42. * @exception {DeveloperError} positions and maximumHeights must have the same length.
  43. * @exception {DeveloperError} positions and minimumHeights must have the same length.
  44. *
  45. * @see WallGeometry#createGeometry
  46. * @see WallGeometry#fromConstantHeight
  47. *
  48. * @demo {@link https://sandcastle.cesium.com/index.html?src=Wall.html|Cesium Sandcastle Wall Demo}
  49. *
  50. * @example
  51. * // create a wall that spans from ground level to 10000 meters
  52. * var wall = new Cesium.WallGeometry({
  53. * positions : Cesium.Cartesian3.fromDegreesArrayHeights([
  54. * 19.0, 47.0, 10000.0,
  55. * 19.0, 48.0, 10000.0,
  56. * 20.0, 48.0, 10000.0,
  57. * 20.0, 47.0, 10000.0,
  58. * 19.0, 47.0, 10000.0
  59. * ])
  60. * });
  61. * var geometry = Cesium.WallGeometry.createGeometry(wall);
  62. */
  63. function WallGeometry(options) {
  64. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  65. var wallPositions = options.positions;
  66. var maximumHeights = options.maximumHeights;
  67. var minimumHeights = options.minimumHeights;
  68. //>>includeStart('debug', pragmas.debug);
  69. if (!defined(wallPositions)) {
  70. throw new DeveloperError('options.positions is required.');
  71. }
  72. if (defined(maximumHeights) && maximumHeights.length !== wallPositions.length) {
  73. throw new DeveloperError('options.positions and options.maximumHeights must have the same length.');
  74. }
  75. if (defined(minimumHeights) && minimumHeights.length !== wallPositions.length) {
  76. throw new DeveloperError('options.positions and options.minimumHeights must have the same length.');
  77. }
  78. //>>includeEnd('debug');
  79. var vertexFormat = defaultValue(options.vertexFormat, VertexFormat.DEFAULT);
  80. var granularity = defaultValue(options.granularity, CesiumMath.RADIANS_PER_DEGREE);
  81. var ellipsoid = defaultValue(options.ellipsoid, Ellipsoid.WGS84);
  82. this._positions = wallPositions;
  83. this._minimumHeights = minimumHeights;
  84. this._maximumHeights = maximumHeights;
  85. this._vertexFormat = VertexFormat.clone(vertexFormat);
  86. this._granularity = granularity;
  87. this._ellipsoid = Ellipsoid.clone(ellipsoid);
  88. this._workerName = 'createWallGeometry';
  89. var numComponents = 1 + wallPositions.length * Cartesian3.packedLength + 2;
  90. if (defined(minimumHeights)) {
  91. numComponents += minimumHeights.length;
  92. }
  93. if (defined(maximumHeights)) {
  94. numComponents += maximumHeights.length;
  95. }
  96. /**
  97. * The number of elements used to pack the object into an array.
  98. * @type {Number}
  99. */
  100. this.packedLength = numComponents + Ellipsoid.packedLength + VertexFormat.packedLength + 1;
  101. }
  102. /**
  103. * Stores the provided instance into the provided array.
  104. *
  105. * @param {WallGeometry} value The value to pack.
  106. * @param {Number[]} array The array to pack into.
  107. * @param {Number} [startingIndex=0] The index into the array at which to start packing the elements.
  108. *
  109. * @returns {Number[]} The array that was packed into
  110. */
  111. WallGeometry.pack = function(value, array, startingIndex) {
  112. //>>includeStart('debug', pragmas.debug);
  113. if (!defined(value)) {
  114. throw new DeveloperError('value is required');
  115. }
  116. if (!defined(array)) {
  117. throw new DeveloperError('array is required');
  118. }
  119. //>>includeEnd('debug');
  120. startingIndex = defaultValue(startingIndex, 0);
  121. var i;
  122. var positions = value._positions;
  123. var length = positions.length;
  124. array[startingIndex++] = length;
  125. for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
  126. Cartesian3.pack(positions[i], array, startingIndex);
  127. }
  128. var minimumHeights = value._minimumHeights;
  129. length = defined(minimumHeights) ? minimumHeights.length : 0;
  130. array[startingIndex++] = length;
  131. if (defined(minimumHeights)) {
  132. for (i = 0; i < length; ++i) {
  133. array[startingIndex++] = minimumHeights[i];
  134. }
  135. }
  136. var maximumHeights = value._maximumHeights;
  137. length = defined(maximumHeights) ? maximumHeights.length : 0;
  138. array[startingIndex++] = length;
  139. if (defined(maximumHeights)) {
  140. for (i = 0; i < length; ++i) {
  141. array[startingIndex++] = maximumHeights[i];
  142. }
  143. }
  144. Ellipsoid.pack(value._ellipsoid, array, startingIndex);
  145. startingIndex += Ellipsoid.packedLength;
  146. VertexFormat.pack(value._vertexFormat, array, startingIndex);
  147. startingIndex += VertexFormat.packedLength;
  148. array[startingIndex] = value._granularity;
  149. return array;
  150. };
  151. var scratchEllipsoid = Ellipsoid.clone(Ellipsoid.UNIT_SPHERE);
  152. var scratchVertexFormat = new VertexFormat();
  153. var scratchOptions = {
  154. positions : undefined,
  155. minimumHeights : undefined,
  156. maximumHeights : undefined,
  157. ellipsoid : scratchEllipsoid,
  158. vertexFormat : scratchVertexFormat,
  159. granularity : undefined
  160. };
  161. /**
  162. * Retrieves an instance from a packed array.
  163. *
  164. * @param {Number[]} array The packed array.
  165. * @param {Number} [startingIndex=0] The starting index of the element to be unpacked.
  166. * @param {WallGeometry} [result] The object into which to store the result.
  167. * @returns {WallGeometry} The modified result parameter or a new WallGeometry instance if one was not provided.
  168. */
  169. WallGeometry.unpack = function(array, startingIndex, result) {
  170. //>>includeStart('debug', pragmas.debug);
  171. if (!defined(array)) {
  172. throw new DeveloperError('array is required');
  173. }
  174. //>>includeEnd('debug');
  175. startingIndex = defaultValue(startingIndex, 0);
  176. var i;
  177. var length = array[startingIndex++];
  178. var positions = new Array(length);
  179. for (i = 0; i < length; ++i, startingIndex += Cartesian3.packedLength) {
  180. positions[i] = Cartesian3.unpack(array, startingIndex);
  181. }
  182. length = array[startingIndex++];
  183. var minimumHeights;
  184. if (length > 0) {
  185. minimumHeights = new Array(length);
  186. for (i = 0; i < length; ++i) {
  187. minimumHeights[i] = array[startingIndex++];
  188. }
  189. }
  190. length = array[startingIndex++];
  191. var maximumHeights;
  192. if (length > 0) {
  193. maximumHeights = new Array(length);
  194. for (i = 0; i < length; ++i) {
  195. maximumHeights[i] = array[startingIndex++];
  196. }
  197. }
  198. var ellipsoid = Ellipsoid.unpack(array, startingIndex, scratchEllipsoid);
  199. startingIndex += Ellipsoid.packedLength;
  200. var vertexFormat = VertexFormat.unpack(array, startingIndex, scratchVertexFormat);
  201. startingIndex += VertexFormat.packedLength;
  202. var granularity = array[startingIndex];
  203. if (!defined(result)) {
  204. scratchOptions.positions = positions;
  205. scratchOptions.minimumHeights = minimumHeights;
  206. scratchOptions.maximumHeights = maximumHeights;
  207. scratchOptions.granularity = granularity;
  208. return new WallGeometry(scratchOptions);
  209. }
  210. result._positions = positions;
  211. result._minimumHeights = minimumHeights;
  212. result._maximumHeights = maximumHeights;
  213. result._ellipsoid = Ellipsoid.clone(ellipsoid, result._ellipsoid);
  214. result._vertexFormat = VertexFormat.clone(vertexFormat, result._vertexFormat);
  215. result._granularity = granularity;
  216. return result;
  217. };
  218. /**
  219. * A description of a wall, which is similar to a KML line string. A wall is defined by a series of points,
  220. * which extrude down to the ground. Optionally, they can extrude downwards to a specified height.
  221. *
  222. * @param {Object} options Object with the following properties:
  223. * @param {Cartesian3[]} options.positions An array of Cartesian objects, which are the points of the wall.
  224. * @param {Number} [options.maximumHeight] A constant that defines the maximum height of the
  225. * wall at <code>positions</code>. If undefined, the height of each position in used.
  226. * @param {Number} [options.minimumHeight] A constant that defines the minimum height of the
  227. * wall at <code>positions</code>. If undefined, the height at each position is 0.0.
  228. * @param {Ellipsoid} [options.ellipsoid=Ellipsoid.WGS84] The ellipsoid for coordinate manipulation
  229. * @param {VertexFormat} [options.vertexFormat=VertexFormat.DEFAULT] The vertex attributes to be computed.
  230. * @returns {WallGeometry}
  231. *
  232. *
  233. * @example
  234. * // create a wall that spans from 10000 meters to 20000 meters
  235. * var wall = Cesium.WallGeometry.fromConstantHeights({
  236. * positions : Cesium.Cartesian3.fromDegreesArray([
  237. * 19.0, 47.0,
  238. * 19.0, 48.0,
  239. * 20.0, 48.0,
  240. * 20.0, 47.0,
  241. * 19.0, 47.0,
  242. * ]),
  243. * minimumHeight : 20000.0,
  244. * maximumHeight : 10000.0
  245. * });
  246. * var geometry = Cesium.WallGeometry.createGeometry(wall);
  247. *
  248. * @see WallGeometry#createGeometry
  249. */
  250. WallGeometry.fromConstantHeights = function(options) {
  251. options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  252. var positions = options.positions;
  253. //>>includeStart('debug', pragmas.debug);
  254. if (!defined(positions)) {
  255. throw new DeveloperError('options.positions is required.');
  256. }
  257. //>>includeEnd('debug');
  258. var minHeights;
  259. var maxHeights;
  260. var min = options.minimumHeight;
  261. var max = options.maximumHeight;
  262. var doMin = defined(min);
  263. var doMax = defined(max);
  264. if (doMin || doMax) {
  265. var length = positions.length;
  266. minHeights = (doMin) ? new Array(length) : undefined;
  267. maxHeights = (doMax) ? new Array(length) : undefined;
  268. for (var i = 0; i < length; ++i) {
  269. if (doMin) {
  270. minHeights[i] = min;
  271. }
  272. if (doMax) {
  273. maxHeights[i] = max;
  274. }
  275. }
  276. }
  277. var newOptions = {
  278. positions : positions,
  279. maximumHeights : maxHeights,
  280. minimumHeights : minHeights,
  281. ellipsoid : options.ellipsoid,
  282. vertexFormat : options.vertexFormat
  283. };
  284. return new WallGeometry(newOptions);
  285. };
  286. /**
  287. * Computes the geometric representation of a wall, including its vertices, indices, and a bounding sphere.
  288. *
  289. * @param {WallGeometry} wallGeometry A description of the wall.
  290. * @returns {Geometry|undefined} The computed vertices and indices.
  291. */
  292. WallGeometry.createGeometry = function(wallGeometry) {
  293. var wallPositions = wallGeometry._positions;
  294. var minimumHeights = wallGeometry._minimumHeights;
  295. var maximumHeights = wallGeometry._maximumHeights;
  296. var vertexFormat = wallGeometry._vertexFormat;
  297. var granularity = wallGeometry._granularity;
  298. var ellipsoid = wallGeometry._ellipsoid;
  299. var pos = WallGeometryLibrary.computePositions(ellipsoid, wallPositions, maximumHeights, minimumHeights, granularity, true);
  300. if (!defined(pos)) {
  301. return;
  302. }
  303. var bottomPositions = pos.bottomPositions;
  304. var topPositions = pos.topPositions;
  305. var numCorners = pos.numCorners;
  306. var length = topPositions.length;
  307. var size = length * 2;
  308. var positions = vertexFormat.position ? new Float64Array(size) : undefined;
  309. var normals = vertexFormat.normal ? new Float32Array(size) : undefined;
  310. var tangents = vertexFormat.tangent ? new Float32Array(size) : undefined;
  311. var bitangents = vertexFormat.bitangent ? new Float32Array(size) : undefined;
  312. var textureCoordinates = vertexFormat.st ? new Float32Array(size / 3 * 2) : undefined;
  313. var positionIndex = 0;
  314. var normalIndex = 0;
  315. var bitangentIndex = 0;
  316. var tangentIndex = 0;
  317. var stIndex = 0;
  318. // add lower and upper points one after the other, lower
  319. // points being even and upper points being odd
  320. var normal = scratchNormal;
  321. var tangent = scratchTangent;
  322. var bitangent = scratchBitangent;
  323. var recomputeNormal = true;
  324. length /= 3;
  325. var i;
  326. var s = 0;
  327. var ds = 1/(length - wallPositions.length + 1);
  328. for (i = 0; i < length; ++i) {
  329. var i3 = i * 3;
  330. var topPosition = Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position1);
  331. var bottomPosition = Cartesian3.fromArray(bottomPositions, i3, scratchCartesian3Position2);
  332. if (vertexFormat.position) {
  333. // insert the lower point
  334. positions[positionIndex++] = bottomPosition.x;
  335. positions[positionIndex++] = bottomPosition.y;
  336. positions[positionIndex++] = bottomPosition.z;
  337. // insert the upper point
  338. positions[positionIndex++] = topPosition.x;
  339. positions[positionIndex++] = topPosition.y;
  340. positions[positionIndex++] = topPosition.z;
  341. }
  342. if (vertexFormat.st) {
  343. textureCoordinates[stIndex++] = s;
  344. textureCoordinates[stIndex++] = 0.0;
  345. textureCoordinates[stIndex++] = s;
  346. textureCoordinates[stIndex++] = 1.0;
  347. }
  348. if (vertexFormat.normal || vertexFormat.tangent || vertexFormat.bitangent) {
  349. var nextPosition;
  350. var nextTop = Cartesian3.clone(Cartesian3.ZERO, scratchCartesian3Position5);
  351. var groundPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3, scratchCartesian3Position2), scratchCartesian3Position2);
  352. if (i + 1 < length) {
  353. nextPosition = ellipsoid.scaleToGeodeticSurface(Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position3), scratchCartesian3Position3);
  354. nextTop = Cartesian3.fromArray(topPositions, i3 + 3, scratchCartesian3Position5);
  355. }
  356. if (recomputeNormal) {
  357. var scalednextPosition = Cartesian3.subtract(nextTop, topPosition, scratchCartesian3Position4);
  358. var scaledGroundPosition = Cartesian3.subtract(groundPosition, topPosition, scratchCartesian3Position1);
  359. normal = Cartesian3.normalize(Cartesian3.cross(scaledGroundPosition, scalednextPosition, normal), normal);
  360. recomputeNormal = false;
  361. }
  362. if (Cartesian3.equalsEpsilon(nextPosition, groundPosition, CesiumMath.EPSILON10)) {
  363. recomputeNormal = true;
  364. } else {
  365. s += ds;
  366. if (vertexFormat.tangent) {
  367. tangent = Cartesian3.normalize(Cartesian3.subtract(nextPosition, groundPosition, tangent), tangent);
  368. }
  369. if (vertexFormat.bitangent) {
  370. bitangent = Cartesian3.normalize(Cartesian3.cross(normal, tangent, bitangent), bitangent);
  371. }
  372. }
  373. if (vertexFormat.normal) {
  374. normals[normalIndex++] = normal.x;
  375. normals[normalIndex++] = normal.y;
  376. normals[normalIndex++] = normal.z;
  377. normals[normalIndex++] = normal.x;
  378. normals[normalIndex++] = normal.y;
  379. normals[normalIndex++] = normal.z;
  380. }
  381. if (vertexFormat.tangent) {
  382. tangents[tangentIndex++] = tangent.x;
  383. tangents[tangentIndex++] = tangent.y;
  384. tangents[tangentIndex++] = tangent.z;
  385. tangents[tangentIndex++] = tangent.x;
  386. tangents[tangentIndex++] = tangent.y;
  387. tangents[tangentIndex++] = tangent.z;
  388. }
  389. if (vertexFormat.bitangent) {
  390. bitangents[bitangentIndex++] = bitangent.x;
  391. bitangents[bitangentIndex++] = bitangent.y;
  392. bitangents[bitangentIndex++] = bitangent.z;
  393. bitangents[bitangentIndex++] = bitangent.x;
  394. bitangents[bitangentIndex++] = bitangent.y;
  395. bitangents[bitangentIndex++] = bitangent.z;
  396. }
  397. }
  398. }
  399. var attributes = new GeometryAttributes();
  400. if (vertexFormat.position) {
  401. attributes.position = new GeometryAttribute({
  402. componentDatatype : ComponentDatatype.DOUBLE,
  403. componentsPerAttribute : 3,
  404. values : positions
  405. });
  406. }
  407. if (vertexFormat.normal) {
  408. attributes.normal = new GeometryAttribute({
  409. componentDatatype : ComponentDatatype.FLOAT,
  410. componentsPerAttribute : 3,
  411. values : normals
  412. });
  413. }
  414. if (vertexFormat.tangent) {
  415. attributes.tangent = new GeometryAttribute({
  416. componentDatatype : ComponentDatatype.FLOAT,
  417. componentsPerAttribute : 3,
  418. values : tangents
  419. });
  420. }
  421. if (vertexFormat.bitangent) {
  422. attributes.bitangent = new GeometryAttribute({
  423. componentDatatype : ComponentDatatype.FLOAT,
  424. componentsPerAttribute : 3,
  425. values : bitangents
  426. });
  427. }
  428. if (vertexFormat.st) {
  429. attributes.st = new GeometryAttribute({
  430. componentDatatype : ComponentDatatype.FLOAT,
  431. componentsPerAttribute : 2,
  432. values : textureCoordinates
  433. });
  434. }
  435. // prepare the side walls, two triangles for each wall
  436. //
  437. // A (i+1) B (i+3) E
  438. // +--------+-------+
  439. // | / | /| triangles: A C B
  440. // | / | / | B C D
  441. // | / | / |
  442. // | / | / |
  443. // | / | / |
  444. // | / | / |
  445. // +--------+-------+
  446. // C (i) D (i+2) F
  447. //
  448. var numVertices = size / 3;
  449. size -= 6 * (numCorners + 1);
  450. var indices = IndexDatatype.createTypedArray(numVertices, size);
  451. var edgeIndex = 0;
  452. for (i = 0; i < numVertices - 2; i += 2) {
  453. var LL = i;
  454. var LR = i + 2;
  455. var pl = Cartesian3.fromArray(positions, LL * 3, scratchCartesian3Position1);
  456. var pr = Cartesian3.fromArray(positions, LR * 3, scratchCartesian3Position2);
  457. if (Cartesian3.equalsEpsilon(pl, pr, CesiumMath.EPSILON10)) {
  458. continue;
  459. }
  460. var UL = i + 1;
  461. var UR = i + 3;
  462. indices[edgeIndex++] = UL;
  463. indices[edgeIndex++] = LL;
  464. indices[edgeIndex++] = UR;
  465. indices[edgeIndex++] = UR;
  466. indices[edgeIndex++] = LL;
  467. indices[edgeIndex++] = LR;
  468. }
  469. return new Geometry({
  470. attributes : attributes,
  471. indices : indices,
  472. primitiveType : PrimitiveType.TRIANGLES,
  473. boundingSphere : new BoundingSphere.fromVertices(positions)
  474. });
  475. };
  476. export default WallGeometry;