智慧水务管理系统 - 精河县供水工程综合管理平台

VoxelCylinderShape.js 27KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869
  1. import BoundingSphere from "../Core/BoundingSphere.js";
  2. import Cartesian2 from "../Core/Cartesian2.js";
  3. import Cartesian3 from "../Core/Cartesian3.js";
  4. import Cartesian4 from "../Core/Cartesian4.js";
  5. import CesiumMath from "../Core/Math.js";
  6. import Check from "../Core/Check.js";
  7. import ClippingPlane from "./ClippingPlane.js";
  8. import Matrix3 from "../Core/Matrix3.js";
  9. import Matrix4 from "../Core/Matrix4.js";
  10. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  11. import VoxelBoundsCollection from "./VoxelBoundsCollection.js";
  12. /**
  13. * A cylinder {@link VoxelShape}.
  14. *
  15. * @alias VoxelCylinderShape
  16. * @constructor
  17. *
  18. * @see VoxelShape
  19. * @see VoxelBoxShape
  20. * @see VoxelEllipsoidShape
  21. * @see VoxelShapeType
  22. *
  23. * @private
  24. */
  25. function VoxelCylinderShape() {
  26. this._orientedBoundingBox = new OrientedBoundingBox();
  27. this._boundingSphere = new BoundingSphere();
  28. this._boundTransform = new Matrix4();
  29. this._shapeTransform = new Matrix4();
  30. /**
  31. * The minimum bounds of the shape, corresponding to minimum radius, angle, and height.
  32. * @type {Cartesian3}
  33. * @private
  34. */
  35. this._minBounds = VoxelCylinderShape.DefaultMinBounds.clone();
  36. /**
  37. * The maximum bounds of the shape, corresponding to maximum radius, angle, and height.
  38. * @type {Cartesian3}
  39. * @private
  40. */
  41. this._maxBounds = VoxelCylinderShape.DefaultMaxBounds.clone();
  42. const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
  43. const boundPlanes = [
  44. new ClippingPlane(
  45. Cartesian3.negate(Cartesian3.UNIT_Z, new Cartesian3()),
  46. DefaultMinBounds.z,
  47. ),
  48. new ClippingPlane(Cartesian3.UNIT_Z, -DefaultMaxBounds.z),
  49. ];
  50. this._renderBoundPlanes = new VoxelBoundsCollection({ planes: boundPlanes });
  51. /**
  52. * UV space transformation translations (JS-only, not shader uniforms)
  53. * Components: [radius, angle, height] translation
  54. * @type {Cartesian3}
  55. * @private
  56. */
  57. this._localToShapeUvTranslate = new Cartesian3();
  58. this._shaderUniforms = {
  59. cameraShapePosition: new Cartesian3(),
  60. cylinderEcToRadialTangentUp: new Matrix3(),
  61. cylinderRenderRadiusMinMax: new Cartesian2(),
  62. cylinderRenderAngleMinMax: new Cartesian2(),
  63. cylinderLocalToShapeUvScale: new Cartesian3(),
  64. cylinderShapeUvAngleRangeOrigin: 0.0,
  65. };
  66. this._shaderDefines = {
  67. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
  68. CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
  69. CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
  70. CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
  71. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
  72. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
  73. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
  74. CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
  75. CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
  76. CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
  77. };
  78. this._shaderMaximumIntersectionsLength = 0; // not known until update
  79. }
  80. Object.defineProperties(VoxelCylinderShape.prototype, {
  81. /**
  82. * An oriented bounding box containing the bounded shape.
  83. *
  84. * @memberof VoxelCylinderShape.prototype
  85. * @type {OrientedBoundingBox}
  86. * @readonly
  87. * @private
  88. */
  89. orientedBoundingBox: {
  90. get: function () {
  91. return this._orientedBoundingBox;
  92. },
  93. },
  94. /**
  95. * A collection of planes used for the render bounds
  96. * @memberof VoxelCylinderShape.prototype
  97. * @type {VoxelBoundsCollection}
  98. * @readonly
  99. * @private
  100. */
  101. renderBoundPlanes: {
  102. get: function () {
  103. return this._renderBoundPlanes;
  104. },
  105. },
  106. /**
  107. * A bounding sphere containing the bounded shape.
  108. *
  109. * @memberof VoxelCylinderShape.prototype
  110. * @type {BoundingSphere}
  111. * @readonly
  112. * @private
  113. */
  114. boundingSphere: {
  115. get: function () {
  116. return this._boundingSphere;
  117. },
  118. },
  119. /**
  120. * A transformation matrix containing the bounded shape.
  121. *
  122. * @memberof VoxelCylinderShape.prototype
  123. * @type {Matrix4}
  124. * @readonly
  125. * @private
  126. */
  127. boundTransform: {
  128. get: function () {
  129. return this._boundTransform;
  130. },
  131. },
  132. /**
  133. * A transformation matrix containing the shape, ignoring the bounds.
  134. *
  135. * @memberof VoxelCylinderShape.prototype
  136. * @type {Matrix4}
  137. * @readonly
  138. * @private
  139. */
  140. shapeTransform: {
  141. get: function () {
  142. return this._shapeTransform;
  143. },
  144. },
  145. /**
  146. * @memberof VoxelCylinderShape.prototype
  147. * @type {Object<string, any>}
  148. * @readonly
  149. * @private
  150. */
  151. shaderUniforms: {
  152. get: function () {
  153. return this._shaderUniforms;
  154. },
  155. },
  156. /**
  157. * @memberof VoxelCylinderShape.prototype
  158. * @type {Object<string, any>}
  159. * @readonly
  160. * @private
  161. */
  162. shaderDefines: {
  163. get: function () {
  164. return this._shaderDefines;
  165. },
  166. },
  167. /**
  168. * The maximum number of intersections against the shape for any ray direction.
  169. * @memberof VoxelCylinderShape.prototype
  170. * @type {number}
  171. * @readonly
  172. * @private
  173. */
  174. shaderMaximumIntersectionsLength: {
  175. get: function () {
  176. return this._shaderMaximumIntersectionsLength;
  177. },
  178. },
  179. });
  180. const scratchScale = new Cartesian3();
  181. const scratchClipMinBounds = new Cartesian3();
  182. const scratchClipMaxBounds = new Cartesian3();
  183. const scratchRenderMinBounds = new Cartesian3();
  184. const scratchRenderMaxBounds = new Cartesian3();
  185. const scratchTransformPositionWorldToLocal = new Matrix4();
  186. const scratchCameraPositionLocal = new Cartesian3();
  187. const scratchCameraRadialPosition = new Cartesian2();
  188. /**
  189. * Update the shape's state.
  190. * @private
  191. * @param {Matrix4} modelMatrix The model matrix.
  192. * @param {Cartesian3} minBounds The minimum bounds.
  193. * @param {Cartesian3} maxBounds The maximum bounds.
  194. * @param {Cartesian3} [clipMinBounds] The minimum clip bounds.
  195. * @param {Cartesian3} [clipMaxBounds] The maximum clip bounds.
  196. * @returns {boolean} Whether the shape is visible.
  197. */
  198. VoxelCylinderShape.prototype.update = function (
  199. modelMatrix,
  200. minBounds,
  201. maxBounds,
  202. clipMinBounds,
  203. clipMaxBounds,
  204. ) {
  205. //>>includeStart('debug', pragmas.debug);
  206. Check.typeOf.object("modelMatrix", modelMatrix);
  207. Check.typeOf.object("minBounds", minBounds);
  208. Check.typeOf.object("maxBounds", maxBounds);
  209. //>>includeEnd('debug');
  210. clipMinBounds = clipMinBounds ?? minBounds.clone(scratchClipMinBounds);
  211. clipMaxBounds = clipMaxBounds ?? maxBounds.clone(scratchClipMaxBounds);
  212. minBounds = Cartesian3.clone(minBounds, this._minBounds);
  213. maxBounds = Cartesian3.clone(maxBounds, this._maxBounds);
  214. const { DefaultMinBounds, DefaultMaxBounds } = VoxelCylinderShape;
  215. const defaultAngleRange = DefaultMaxBounds.y - DefaultMinBounds.y; // == 2 * PI
  216. const defaultAngleRangeHalf = 0.5 * defaultAngleRange; // == PI
  217. const epsilonZeroScale = CesiumMath.EPSILON10;
  218. const epsilonAngle = CesiumMath.EPSILON10;
  219. // Clamp the bounds to the valid range
  220. minBounds.x = Math.max(0.0, minBounds.x);
  221. // TODO: require maxBounds.x >= minBounds.x ?
  222. maxBounds.x = Math.max(0.0, maxBounds.x);
  223. minBounds.y = CesiumMath.negativePiToPi(minBounds.y);
  224. maxBounds.y = CesiumMath.negativePiToPi(maxBounds.y);
  225. clipMinBounds.y = CesiumMath.negativePiToPi(clipMinBounds.y);
  226. clipMaxBounds.y = CesiumMath.negativePiToPi(clipMaxBounds.y);
  227. // TODO: what does this do with partial volumes crossing the antimeridian?
  228. // We could have minBounds.y = +PI/2 and maxBounds.y = -PI/2.
  229. // Then clipMinBounds.y = +PI/4 and clipMaxBounds.y = -PI/4.
  230. // This maximumByComponent would cancel the clipping.
  231. const renderMinBounds = Cartesian3.maximumByComponent(
  232. minBounds,
  233. clipMinBounds,
  234. scratchRenderMinBounds,
  235. );
  236. const renderMaxBounds = Cartesian3.minimumByComponent(
  237. maxBounds,
  238. clipMaxBounds,
  239. scratchRenderMaxBounds,
  240. );
  241. // Exit early if the shape is not visible.
  242. // Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
  243. // Cylinder is not visible if:
  244. // - maxRadius is zero (line)
  245. // - minRadius is greater than maxRadius
  246. // - minHeight is greater than maxHeight
  247. // - scale is 0 for any component (too annoying to reconstruct rotation matrix)
  248. const scale = Matrix4.getScale(modelMatrix, scratchScale);
  249. if (
  250. renderMaxBounds.x === 0.0 ||
  251. renderMinBounds.x > renderMaxBounds.x ||
  252. renderMinBounds.z > renderMaxBounds.z ||
  253. CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, epsilonZeroScale) ||
  254. CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, epsilonZeroScale) ||
  255. CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, epsilonZeroScale)
  256. ) {
  257. return false;
  258. }
  259. // Update the render bounds planes
  260. const renderBoundPlanes = this._renderBoundPlanes;
  261. renderBoundPlanes.get(0).distance = renderMinBounds.z;
  262. renderBoundPlanes.get(1).distance = -renderMaxBounds.z;
  263. this._shapeTransform = Matrix4.clone(modelMatrix, this._shapeTransform);
  264. this._orientedBoundingBox = getCylinderChunkObb(
  265. renderMinBounds,
  266. renderMaxBounds,
  267. this._shapeTransform,
  268. this._orientedBoundingBox,
  269. );
  270. this._boundTransform = Matrix4.fromRotationTranslation(
  271. this._orientedBoundingBox.halfAxes,
  272. this._orientedBoundingBox.center,
  273. this._boundTransform,
  274. );
  275. this._boundingSphere = BoundingSphere.fromOrientedBoundingBox(
  276. this._orientedBoundingBox,
  277. this._boundingSphere,
  278. );
  279. const shapeIsAngleReversed = maxBounds.y < minBounds.y;
  280. const shapeAngleRange =
  281. maxBounds.y - minBounds.y + shapeIsAngleReversed * defaultAngleRange;
  282. const renderIsAngleReversed = renderMaxBounds.y < renderMinBounds.y;
  283. const renderAngleRange =
  284. renderMaxBounds.y -
  285. renderMinBounds.y +
  286. renderIsAngleReversed * defaultAngleRange;
  287. const renderIsAngleRegular =
  288. renderAngleRange >= defaultAngleRangeHalf - epsilonAngle &&
  289. renderAngleRange < defaultAngleRange - epsilonAngle;
  290. const renderIsAngleFlipped =
  291. renderAngleRange > epsilonAngle &&
  292. renderAngleRange < defaultAngleRangeHalf - epsilonAngle;
  293. const renderIsAngleRangeZero = renderAngleRange <= epsilonAngle;
  294. const renderHasAngle =
  295. renderIsAngleRegular || renderIsAngleFlipped || renderIsAngleRangeZero;
  296. const shaderUniforms = this._shaderUniforms;
  297. const shaderDefines = this._shaderDefines;
  298. // To keep things simple, clear the defines every time
  299. for (const key in shaderDefines) {
  300. if (shaderDefines.hasOwnProperty(key)) {
  301. shaderDefines[key] = undefined;
  302. }
  303. }
  304. // Keep track of how many intersections there are going to be.
  305. let intersectionCount = 0;
  306. shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MAX"] = intersectionCount;
  307. intersectionCount += 1;
  308. if (shapeAngleRange < defaultAngleRange - epsilonAngle) {
  309. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
  310. }
  311. if (renderMinBounds.x !== DefaultMinBounds.x) {
  312. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true;
  313. shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
  314. intersectionCount += 1;
  315. }
  316. shaderUniforms.cylinderRenderRadiusMinMax = Cartesian2.fromElements(
  317. renderMinBounds.x,
  318. renderMaxBounds.x,
  319. shaderUniforms.cylinderRenderRadiusMinMax,
  320. );
  321. if (renderMinBounds.x === renderMaxBounds.x) {
  322. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true;
  323. }
  324. const radiusRange = maxBounds.x - minBounds.x;
  325. // Defaults are for the case where radiusRange is zero, to avoid division by zero.
  326. let radialScale = 0.0;
  327. let radialOffset = 1.0;
  328. if (radiusRange !== 0.0) {
  329. radialScale = 1.0 / radiusRange;
  330. radialOffset = -minBounds.x * radialScale;
  331. }
  332. const heightRange = maxBounds.z - minBounds.z; // Default 2.0
  333. // Defaults are for the case where heightRange is zero, to avoid division by zero.
  334. let heightScale = 0.0;
  335. let heightOffset = 1.0;
  336. if (heightRange !== 0.0) {
  337. heightScale = 1.0 / heightRange;
  338. heightOffset = -minBounds.z * heightScale;
  339. }
  340. if (renderHasAngle) {
  341. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE"] = true;
  342. shaderDefines["CYLINDER_INTERSECTION_INDEX_ANGLE"] = intersectionCount;
  343. if (renderIsAngleRegular) {
  344. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF"] = true;
  345. intersectionCount += 1;
  346. } else if (renderIsAngleFlipped) {
  347. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF"] = true;
  348. intersectionCount += 2;
  349. } else if (renderIsAngleRangeZero) {
  350. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO"] = true;
  351. intersectionCount += 2;
  352. }
  353. shaderUniforms.cylinderRenderAngleMinMax = Cartesian2.fromElements(
  354. renderMinBounds.y,
  355. renderMaxBounds.y,
  356. shaderUniforms.cylinderRenderAngleMinMax,
  357. );
  358. }
  359. const uvMinAngle = (minBounds.y - DefaultMinBounds.y) / defaultAngleRange;
  360. const uvMaxAngle = (maxBounds.y - DefaultMinBounds.y) / defaultAngleRange;
  361. const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
  362. // Translate the origin of UV angles (in [0,1]) to the center of the unoccupied space
  363. const uvAngleRangeOrigin = (uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
  364. shaderUniforms.cylinderShapeUvAngleRangeOrigin = uvAngleRangeOrigin;
  365. // Defaults are for the case where shapeAngleRange is zero, to avoid division by zero.
  366. let angleScale = 0.0;
  367. let angleOffset = 1.0;
  368. if (shapeAngleRange > epsilonAngle) {
  369. angleScale = defaultAngleRange / shapeAngleRange;
  370. const shiftedMinAngle = uvMinAngle - uvAngleRangeOrigin;
  371. angleOffset = -angleScale * (shiftedMinAngle - Math.floor(shiftedMinAngle));
  372. }
  373. // Store scales in shader uniforms (GPU)
  374. shaderUniforms.cylinderLocalToShapeUvScale = Cartesian3.fromElements(
  375. radialScale,
  376. angleScale,
  377. heightScale,
  378. shaderUniforms.cylinderLocalToShapeUvScale,
  379. );
  380. // Store translations in private property (JS-only)
  381. this._localToShapeUvTranslate = Cartesian3.fromElements(
  382. radialOffset,
  383. angleOffset,
  384. heightOffset,
  385. this._localToShapeUvTranslate,
  386. );
  387. this._shaderMaximumIntersectionsLength = intersectionCount;
  388. return true;
  389. };
  390. const scratchRotateRtuToLocal = new Matrix3();
  391. const scratchRtuRotation = new Matrix3();
  392. const scratchTransformPositionViewToLocal = new Matrix4();
  393. /**
  394. * Update any view-dependent transforms.
  395. * @private
  396. * @param {FrameState} frameState The frame state.
  397. */
  398. VoxelCylinderShape.prototype.updateViewTransforms = function (frameState) {
  399. const shaderUniforms = this._shaderUniforms;
  400. // 1. Update camera position in cylindrical coordinates
  401. const transformPositionWorldToLocal = Matrix4.inverse(
  402. this._shapeTransform,
  403. scratchTransformPositionWorldToLocal,
  404. );
  405. const cameraPositionLocal = Matrix4.multiplyByPoint(
  406. transformPositionWorldToLocal,
  407. frameState.camera.positionWC,
  408. scratchCameraPositionLocal,
  409. );
  410. shaderUniforms.cameraShapePosition = Cartesian3.fromElements(
  411. Cartesian2.magnitude(cameraPositionLocal),
  412. Math.atan2(cameraPositionLocal.y, cameraPositionLocal.x),
  413. cameraPositionLocal.z,
  414. shaderUniforms.cameraShapePosition,
  415. );
  416. // 2. Find radial, tangent, and up components at camera position
  417. const cameraRadialDirection = Cartesian2.normalize(
  418. Cartesian2.fromCartesian3(cameraPositionLocal, scratchCameraRadialPosition),
  419. scratchCameraRadialPosition,
  420. );
  421. // As row vectors, the radial, tangent, and up vectors constitute a rotation matrix from local to RTU.
  422. const rotateLocalToRtu = Matrix3.fromRowMajorArray(
  423. [
  424. cameraRadialDirection.x,
  425. cameraRadialDirection.y,
  426. 0.0,
  427. -cameraRadialDirection.y,
  428. cameraRadialDirection.x,
  429. 0.0,
  430. 0.0,
  431. 0.0,
  432. 1.0,
  433. ],
  434. scratchRotateRtuToLocal,
  435. );
  436. // 3. Get rotation from eye to local coordinates
  437. const transformPositionViewToWorld =
  438. frameState.context.uniformState.inverseView;
  439. const transformPositionViewToLocal = Matrix4.multiplyTransformation(
  440. transformPositionWorldToLocal,
  441. transformPositionViewToWorld,
  442. scratchTransformPositionViewToLocal,
  443. );
  444. const transformDirectionViewToLocal = Matrix4.getMatrix3(
  445. transformPositionViewToLocal,
  446. scratchRtuRotation,
  447. );
  448. // 4. Multiply to get rotation from eye to RTU coordinates
  449. shaderUniforms.cylinderEcToRadialTangentUp = Matrix3.multiply(
  450. rotateLocalToRtu,
  451. transformDirectionViewToLocal,
  452. shaderUniforms.cylinderEcToRadialTangentUp,
  453. );
  454. };
  455. /**
  456. * Convert a UV coordinate to the shape's UV space.
  457. * @private
  458. * @param {Cartesian3} positionLocal The local coordinate to convert.
  459. * @param {Cartesian3} result The Cartesian3 to store the result in.
  460. * @returns {Cartesian3} The converted UV coordinate.
  461. */
  462. VoxelCylinderShape.prototype.convertLocalToShapeUvSpace = function (
  463. positionLocal,
  464. result,
  465. ) {
  466. //>>includeStart('debug', pragmas.debug);
  467. Check.typeOf.object("positionLocal", positionLocal);
  468. Check.typeOf.object("result", result);
  469. //>>includeEnd('debug');
  470. let radius = Math.hypot(positionLocal.x, positionLocal.y);
  471. let angle = Math.atan2(positionLocal.y, positionLocal.x);
  472. let height = positionLocal.z;
  473. const { cylinderLocalToShapeUvScale, cylinderShapeUvAngleRangeOrigin } =
  474. this._shaderUniforms;
  475. const localToShapeUvTranslate = this._localToShapeUvTranslate;
  476. radius = radius * cylinderLocalToShapeUvScale.x + localToShapeUvTranslate.x;
  477. // Convert angle to a "UV" in [0,1] with 0 defined at the center of the unoccupied space.
  478. angle = (angle + Math.PI) / (2.0 * Math.PI);
  479. angle -= cylinderShapeUvAngleRangeOrigin;
  480. angle = angle - Math.floor(angle);
  481. // Scale and shift so [0,1] covers the occupied space.
  482. angle = angle * cylinderLocalToShapeUvScale.y + localToShapeUvTranslate.y;
  483. height = height * cylinderLocalToShapeUvScale.z + localToShapeUvTranslate.z;
  484. return Cartesian3.fromElements(radius, angle, height, result);
  485. };
  486. const scratchMinBounds = new Cartesian3();
  487. const scratchMaxBounds = new Cartesian3();
  488. /**
  489. * Computes an oriented bounding box for a specified tile.
  490. * @private
  491. * @param {number} tileLevel The tile's level.
  492. * @param {number} tileX The tile's x coordinate.
  493. * @param {number} tileY The tile's y coordinate.
  494. * @param {number} tileZ The tile's z coordinate.
  495. * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
  496. * @returns {OrientedBoundingBox} The oriented bounding box.
  497. */
  498. VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
  499. tileLevel,
  500. tileX,
  501. tileY,
  502. tileZ,
  503. result,
  504. ) {
  505. //>>includeStart('debug', pragmas.debug);
  506. Check.typeOf.number("tileLevel", tileLevel);
  507. Check.typeOf.number("tileX", tileX);
  508. Check.typeOf.number("tileY", tileY);
  509. Check.typeOf.number("tileZ", tileZ);
  510. Check.typeOf.object("result", result);
  511. //>>includeEnd('debug');
  512. const minBounds = this._minBounds;
  513. const maxBounds = this._maxBounds;
  514. const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
  515. const tileMinBounds = Cartesian3.fromElements(
  516. CesiumMath.lerp(minBounds.x, maxBounds.x, tileX * sizeAtLevel),
  517. CesiumMath.lerp(minBounds.y, maxBounds.y, tileY * sizeAtLevel),
  518. CesiumMath.lerp(minBounds.z, maxBounds.z, tileZ * sizeAtLevel),
  519. scratchMinBounds,
  520. );
  521. const tileMaxBounds = Cartesian3.fromElements(
  522. CesiumMath.lerp(minBounds.x, maxBounds.x, (tileX + 1) * sizeAtLevel),
  523. CesiumMath.lerp(minBounds.y, maxBounds.y, (tileY + 1) * sizeAtLevel),
  524. CesiumMath.lerp(minBounds.z, maxBounds.z, (tileZ + 1) * sizeAtLevel),
  525. scratchMaxBounds,
  526. );
  527. return getCylinderChunkObb(
  528. tileMinBounds,
  529. tileMaxBounds,
  530. this._shapeTransform,
  531. result,
  532. );
  533. };
  534. const sampleSizeScratch = new Cartesian3();
  535. /**
  536. * Computes an oriented bounding box for a specified sample within a specified tile.
  537. * @private
  538. * @param {SpatialNode} spatialNode The spatial node containing the sample
  539. * @param {Cartesian3} tileDimensions The size of the tile in number of samples, before padding
  540. * @param {Cartesian3} tileUv The sample coordinate within the tile
  541. * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified sample
  542. * @returns {OrientedBoundingBox} The oriented bounding box.
  543. */
  544. VoxelCylinderShape.prototype.computeOrientedBoundingBoxForSample = function (
  545. spatialNode,
  546. tileDimensions,
  547. tileUv,
  548. result,
  549. ) {
  550. //>>includeStart('debug', pragmas.debug);
  551. Check.typeOf.object("spatialNode", spatialNode);
  552. Check.typeOf.object("tileDimensions", tileDimensions);
  553. Check.typeOf.object("tileUv", tileUv);
  554. Check.typeOf.object("result", result);
  555. //>>includeEnd('debug');
  556. const tileSizeAtLevel = 1.0 / Math.pow(2.0, spatialNode.level);
  557. const sampleSize = Cartesian3.divideComponents(
  558. Cartesian3.ONE,
  559. tileDimensions,
  560. sampleSizeScratch,
  561. );
  562. const sampleSizeAtLevel = Cartesian3.multiplyByScalar(
  563. sampleSize,
  564. tileSizeAtLevel,
  565. sampleSizeScratch,
  566. );
  567. const minLerp = Cartesian3.multiplyByScalar(
  568. Cartesian3.fromElements(
  569. spatialNode.x + tileUv.x,
  570. spatialNode.y + tileUv.y,
  571. spatialNode.z + tileUv.z,
  572. scratchMinBounds,
  573. ),
  574. tileSizeAtLevel,
  575. scratchMinBounds,
  576. );
  577. const maxLerp = Cartesian3.add(minLerp, sampleSizeAtLevel, scratchMaxBounds);
  578. const minBounds = this._minBounds;
  579. const maxBounds = this._maxBounds;
  580. const sampleMinBounds = Cartesian3.fromElements(
  581. CesiumMath.lerp(minBounds.x, maxBounds.x, minLerp.x),
  582. CesiumMath.lerp(minBounds.y, maxBounds.y, minLerp.y),
  583. CesiumMath.lerp(minBounds.z, maxBounds.z, minLerp.z),
  584. scratchMinBounds,
  585. );
  586. const sampleMaxBounds = Cartesian3.fromElements(
  587. CesiumMath.lerp(minBounds.x, maxBounds.x, maxLerp.x),
  588. CesiumMath.lerp(minBounds.y, maxBounds.y, maxLerp.y),
  589. CesiumMath.lerp(minBounds.z, maxBounds.z, maxLerp.z),
  590. scratchMaxBounds,
  591. );
  592. return getCylinderChunkObb(
  593. sampleMinBounds,
  594. sampleMaxBounds,
  595. this._shapeTransform,
  596. result,
  597. );
  598. };
  599. /**
  600. * Defines the minimum bounds of the shape. Corresponds to minimum radius, angle, and height.
  601. *
  602. * @type {Cartesian3}
  603. * @constant
  604. * @readonly
  605. *
  606. * @private
  607. */
  608. VoxelCylinderShape.DefaultMinBounds = Object.freeze(
  609. new Cartesian3(0.0, -CesiumMath.PI, -1.0),
  610. );
  611. /**
  612. * Defines the maximum bounds of the shape. Corresponds to maximum radius, angle, height.
  613. *
  614. * @type {Cartesian3}
  615. * @constant
  616. * @readonly
  617. *
  618. * @private
  619. */
  620. VoxelCylinderShape.DefaultMaxBounds = Object.freeze(
  621. new Cartesian3(1.0, +CesiumMath.PI, +1.0),
  622. );
  623. const maxTestAngles = 5;
  624. const scratchTestAngles = new Array(maxTestAngles);
  625. const scratchTranslation = new Cartesian3();
  626. const scratchRotation = new Matrix3();
  627. const scratchTranslationMatrix = new Matrix4();
  628. const scratchRotationMatrix = new Matrix4();
  629. const scratchScaleMatrix = new Matrix4();
  630. const scratchMatrix = new Matrix4();
  631. const scratchColumn0 = new Cartesian3();
  632. const scratchColumn1 = new Cartesian3();
  633. const scratchColumn2 = new Cartesian3();
  634. const scratchCorners = new Array(8);
  635. for (let i = 0; i < 8; i++) {
  636. scratchCorners[i] = new Cartesian3();
  637. }
  638. function orthogonal(a, b, epsilon) {
  639. return Math.abs(Cartesian4.dot(a, b)) < epsilon;
  640. }
  641. function isValidOrientedBoundingBoxTransformation(matrix) {
  642. const column0 = Matrix4.getColumn(matrix, 0, scratchColumn0);
  643. const column1 = Matrix4.getColumn(matrix, 1, scratchColumn1);
  644. const column2 = Matrix4.getColumn(matrix, 2, scratchColumn2);
  645. const epsilon = CesiumMath.EPSILON4;
  646. return (
  647. orthogonal(column0, column1, epsilon) &&
  648. orthogonal(column1, column2, epsilon)
  649. );
  650. }
  651. function computeLooseOrientedBoundingBox(matrix, result) {
  652. const corners = scratchCorners;
  653. Cartesian3.fromElements(-0.5, -0.5, -0.5, corners[0]);
  654. Cartesian3.fromElements(-0.5, -0.5, 0.5, corners[1]);
  655. Cartesian3.fromElements(-0.5, 0.5, -0.5, corners[2]);
  656. Cartesian3.fromElements(-0.5, 0.5, 0.5, corners[3]);
  657. Cartesian3.fromElements(0.5, -0.5, -0.5, corners[4]);
  658. Cartesian3.fromElements(0.5, -0.5, 0.5, corners[5]);
  659. Cartesian3.fromElements(0.5, 0.5, -0.5, corners[6]);
  660. Cartesian3.fromElements(0.5, 0.5, 0.5, corners[7]);
  661. for (let i = 0; i < 8; ++i) {
  662. Matrix4.multiplyByPoint(matrix, corners[i], corners[i]);
  663. }
  664. return OrientedBoundingBox.fromPoints(corners, result);
  665. }
  666. const scratchBoxScale = new Cartesian3();
  667. /**
  668. * Computes an {@link OrientedBoundingBox} for a subregion of the shape.
  669. *
  670. * @function
  671. *
  672. * @param {Cartesian3} chunkMinBounds The minimum bounds of the subregion.
  673. * @param {Cartesian3} chunkMaxBounds The maximum bounds of the subregion.
  674. * @param {Matrix4} matrix The matrix to transform the points.
  675. * @param {OrientedBoundingBox} result The object onto which to store the result.
  676. * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
  677. *
  678. * @private
  679. */
  680. function getCylinderChunkObb(chunkMinBounds, chunkMaxBounds, matrix, result) {
  681. const radiusStart = chunkMinBounds.x;
  682. const radiusEnd = chunkMaxBounds.x;
  683. const angleStart = chunkMinBounds.y;
  684. const angleEnd =
  685. chunkMaxBounds.y < angleStart
  686. ? chunkMaxBounds.y + CesiumMath.TWO_PI
  687. : chunkMaxBounds.y;
  688. const heightStart = chunkMinBounds.z;
  689. const heightEnd = chunkMaxBounds.z;
  690. const angleRange = angleEnd - angleStart;
  691. const angleMid = angleStart + angleRange * 0.5;
  692. const testAngles = scratchTestAngles;
  693. let testAngleCount = 0;
  694. testAngles[testAngleCount++] = angleStart;
  695. testAngles[testAngleCount++] = angleEnd;
  696. testAngles[testAngleCount++] = angleMid;
  697. if (angleRange > CesiumMath.PI) {
  698. testAngles[testAngleCount++] = angleMid - CesiumMath.PI_OVER_TWO;
  699. testAngles[testAngleCount++] = angleMid + CesiumMath.PI_OVER_TWO;
  700. }
  701. // Find bounding box in shape space relative to angleMid
  702. let minX = Number.POSITIVE_INFINITY;
  703. let minY = Number.POSITIVE_INFINITY;
  704. let maxX = Number.NEGATIVE_INFINITY;
  705. let maxY = Number.NEGATIVE_INFINITY;
  706. for (let i = 0; i < testAngleCount; ++i) {
  707. const angle = testAngles[i] - angleMid;
  708. const cosAngle = Math.cos(angle);
  709. const sinAngle = Math.sin(angle);
  710. const x1 = cosAngle * radiusStart;
  711. const y1 = sinAngle * radiusStart;
  712. const x2 = cosAngle * radiusEnd;
  713. const y2 = sinAngle * radiusEnd;
  714. minX = Math.min(minX, x1, x2);
  715. minY = Math.min(minY, y1, y2);
  716. maxX = Math.max(maxX, x1, x2);
  717. maxY = Math.max(maxY, y1, y2);
  718. }
  719. const extentX = maxX - minX;
  720. const extentY = maxY - minY;
  721. const extentZ = heightEnd - heightStart;
  722. const centerX = (minX + maxX) * 0.5;
  723. const centerY = (minY + maxY) * 0.5;
  724. const centerZ = (heightStart + heightEnd) * 0.5;
  725. const translation = Cartesian3.fromElements(
  726. centerX,
  727. centerY,
  728. centerZ,
  729. scratchTranslation,
  730. );
  731. const rotation = Matrix3.fromRotationZ(angleMid, scratchRotation);
  732. const scale = Cartesian3.fromElements(
  733. extentX,
  734. extentY,
  735. extentZ,
  736. scratchBoxScale,
  737. );
  738. const scaleMatrix = Matrix4.fromScale(scale, scratchScaleMatrix);
  739. const rotationMatrix = Matrix4.fromRotation(rotation, scratchRotationMatrix);
  740. const translationMatrix = Matrix4.fromTranslation(
  741. translation,
  742. scratchTranslationMatrix,
  743. );
  744. // Shape space matrix = R * T * S
  745. const localMatrix = Matrix4.multiplyTransformation(
  746. rotationMatrix,
  747. Matrix4.multiplyTransformation(
  748. translationMatrix,
  749. scaleMatrix,
  750. scratchMatrix,
  751. ),
  752. scratchMatrix,
  753. );
  754. const globalMatrix = Matrix4.multiplyTransformation(
  755. matrix,
  756. localMatrix,
  757. scratchMatrix,
  758. );
  759. if (!isValidOrientedBoundingBoxTransformation(globalMatrix)) {
  760. return computeLooseOrientedBoundingBox(globalMatrix, result);
  761. }
  762. return OrientedBoundingBox.fromTransformation(globalMatrix, result);
  763. }
  764. export default VoxelCylinderShape;