VoxelCylinderShape.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937
  1. import defaultValue from "../Core/defaultValue.js";
  2. import BoundingSphere from "../Core/BoundingSphere.js";
  3. import Cartesian2 from "../Core/Cartesian2.js";
  4. import Cartesian3 from "../Core/Cartesian3.js";
  5. import Check from "../Core/Check.js";
  6. import CesiumMath from "../Core/Math.js";
  7. import Matrix3 from "../Core/Matrix3.js";
  8. import Matrix4 from "../Core/Matrix4.js";
  9. import OrientedBoundingBox from "../Core/OrientedBoundingBox.js";
  10. import Cartesian4 from "../Core/Cartesian4.js";
  11. /**
  12. * A cylinder {@link VoxelShape}.
  13. *
  14. * @alias VoxelCylinderShape
  15. * @constructor
  16. *
  17. * @see VoxelShape
  18. * @see VoxelBoxShape
  19. * @see VoxelEllipsoidShape
  20. * @see VoxelShapeType
  21. *
  22. * @private
  23. */
  24. function VoxelCylinderShape() {
  25. /**
  26. * An oriented bounding box containing the bounded shape.
  27. * The update function must be called before accessing this value.
  28. * @type {OrientedBoundingBox}
  29. * @readonly
  30. */
  31. this.orientedBoundingBox = new OrientedBoundingBox();
  32. /**
  33. * A bounding sphere containing the bounded shape.
  34. * The update function must be called before accessing this value.
  35. * @type {BoundingSphere}
  36. * @readonly
  37. */
  38. this.boundingSphere = new BoundingSphere();
  39. /**
  40. * A transformation matrix containing the bounded shape.
  41. * The update function must be called before accessing this value.
  42. * @type {Matrix4}
  43. * @readonly
  44. */
  45. this.boundTransform = new Matrix4();
  46. /**
  47. * A transformation matrix containing the shape, ignoring the bounds.
  48. * The update function must be called before accessing this value.
  49. * @type {Matrix4}
  50. * @readonly
  51. */
  52. this.shapeTransform = new Matrix4();
  53. /**
  54. * @type {number}
  55. * @private
  56. */
  57. this._minimumRadius = VoxelCylinderShape.DefaultMinBounds.x;
  58. /**
  59. * @type {number}
  60. * @private
  61. */
  62. this._maximumRadius = VoxelCylinderShape.DefaultMaxBounds.x;
  63. /**
  64. * @type {number}
  65. * @private
  66. */
  67. this._minimumHeight = VoxelCylinderShape.DefaultMinBounds.y;
  68. /**
  69. * @type {number}
  70. * @private
  71. */
  72. this._maximumHeight = VoxelCylinderShape.DefaultMaxBounds.y;
  73. /**
  74. * @type {number}
  75. * @private
  76. */
  77. this._minimumAngle = VoxelCylinderShape.DefaultMinBounds.z;
  78. /**
  79. * @type {number}
  80. * @private
  81. */
  82. this._maximumAngle = VoxelCylinderShape.DefaultMaxBounds.z;
  83. /**
  84. * @type {Object<string, any>}
  85. * @readonly
  86. */
  87. this.shaderUniforms = {
  88. cylinderUvToRenderBoundsScale: new Cartesian3(),
  89. cylinderUvToRenderBoundsTranslate: new Cartesian3(),
  90. cylinderUvToRenderRadiusMin: 0.0,
  91. cylinderRenderAngleMinMax: new Cartesian2(),
  92. cylinderUvToShapeUvRadius: new Cartesian2(),
  93. cylinderUvToShapeUvHeight: new Cartesian2(),
  94. cylinderUvToShapeUvAngle: new Cartesian2(),
  95. cylinderShapeUvAngleMinMax: new Cartesian2(),
  96. cylinderShapeUvAngleRangeZeroMid: 0.0,
  97. };
  98. /**
  99. * @type {Object<string, any>}
  100. * @readonly
  101. */
  102. this.shaderDefines = {
  103. CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN: undefined,
  104. CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX: undefined,
  105. CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT: undefined,
  106. CYLINDER_HAS_RENDER_BOUNDS_HEIGHT: undefined,
  107. CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT: undefined,
  108. CYLINDER_HAS_RENDER_BOUNDS_ANGLE: undefined,
  109. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
  110. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF: undefined,
  111. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_HALF: undefined,
  112. CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF: undefined,
  113. CYLINDER_HAS_SHAPE_BOUNDS_RADIUS: undefined,
  114. CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT: undefined,
  115. CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT: undefined,
  116. CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT: undefined,
  117. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE: undefined,
  118. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_EQUAL_ZERO: undefined,
  119. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY: undefined,
  120. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY: undefined,
  121. CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED: undefined,
  122. CYLINDER_INTERSECTION_INDEX_RADIUS_MAX: undefined,
  123. CYLINDER_INTERSECTION_INDEX_RADIUS_MIN: undefined,
  124. CYLINDER_INTERSECTION_INDEX_ANGLE: undefined,
  125. };
  126. /**
  127. * The maximum number of intersections against the shape for any ray direction.
  128. * @type {number}
  129. * @readonly
  130. */
  131. this.shaderMaximumIntersectionsLength = 0; // not known until update
  132. }
  133. const scratchScale = new Cartesian3();
  134. const scratchBoundsTranslation = new Cartesian3();
  135. const scratchBoundsScale = new Cartesian3();
  136. const scratchBoundsScaleMatrix = new Matrix3();
  137. const scratchTransformLocalToBounds = new Matrix4();
  138. const scratchTransformUvToBounds = new Matrix4();
  139. const transformUvToLocal = Matrix4.fromRotationTranslation(
  140. Matrix3.fromUniformScale(2.0, new Matrix3()),
  141. new Cartesian3(-1.0, -1.0, -1.0),
  142. new Matrix4()
  143. );
  144. /**
  145. * Update the shape's state.
  146. *
  147. * @param {Matrix4} modelMatrix The model matrix.
  148. * @param {Cartesian3} minBounds The minimum bounds.
  149. * @param {Cartesian3} maxBounds The maximum bounds.
  150. * @param {Cartesian3} [clipMinBounds=VoxelCylinderShape.DefaultMinBounds] The minimum clip bounds.
  151. * @param {Cartesian3} [clipMaxBounds=VoxelCylinderShape.DefaultMaxBounds] The maximum clip bounds.
  152. * @returns {boolean} Whether the shape is visible.
  153. */
  154. VoxelCylinderShape.prototype.update = function (
  155. modelMatrix,
  156. minBounds,
  157. maxBounds,
  158. clipMinBounds,
  159. clipMaxBounds
  160. ) {
  161. clipMinBounds = defaultValue(
  162. clipMinBounds,
  163. VoxelCylinderShape.DefaultMinBounds
  164. );
  165. clipMaxBounds = defaultValue(
  166. clipMaxBounds,
  167. VoxelCylinderShape.DefaultMaxBounds
  168. );
  169. //>>includeStart('debug', pragmas.debug);
  170. Check.typeOf.object("modelMatrix", modelMatrix);
  171. Check.typeOf.object("minBounds", minBounds);
  172. Check.typeOf.object("maxBounds", maxBounds);
  173. //>>includeEnd('debug');
  174. const defaultMinRadius = VoxelCylinderShape.DefaultMinBounds.x;
  175. const defaultMaxRadius = VoxelCylinderShape.DefaultMaxBounds.x;
  176. const defaultMinHeight = VoxelCylinderShape.DefaultMinBounds.y;
  177. const defaultMaxHeight = VoxelCylinderShape.DefaultMaxBounds.y;
  178. const defaultMinAngle = VoxelCylinderShape.DefaultMinBounds.z;
  179. const defaultMaxAngle = VoxelCylinderShape.DefaultMaxBounds.z;
  180. const defaultAngleRange = defaultMaxAngle - defaultMinAngle;
  181. const defaultAngleRangeHalf = 0.5 * defaultAngleRange;
  182. const epsilonZeroScale = CesiumMath.EPSILON10;
  183. const epsilonAngleDiscontinuity = CesiumMath.EPSILON3; // 0.001 radians = 0.05729578 degrees
  184. const epsilonAngle = CesiumMath.EPSILON10;
  185. // Clamp the radii to the valid range
  186. const shapeMinRadius = CesiumMath.clamp(
  187. minBounds.x,
  188. defaultMinRadius,
  189. defaultMaxRadius
  190. );
  191. const shapeMaxRadius = CesiumMath.clamp(
  192. maxBounds.x,
  193. defaultMinRadius,
  194. defaultMaxRadius
  195. );
  196. const clipMinRadius = CesiumMath.clamp(
  197. clipMinBounds.x,
  198. defaultMinRadius,
  199. defaultMaxRadius
  200. );
  201. const clipMaxRadius = CesiumMath.clamp(
  202. clipMaxBounds.x,
  203. defaultMinRadius,
  204. defaultMaxRadius
  205. );
  206. const renderMinRadius = Math.max(shapeMinRadius, clipMinRadius);
  207. const renderMaxRadius = Math.min(shapeMaxRadius, clipMaxRadius);
  208. // Clamp the heights to the valid range
  209. const shapeMinHeight = CesiumMath.clamp(
  210. minBounds.y,
  211. defaultMinHeight,
  212. defaultMaxHeight
  213. );
  214. const shapeMaxHeight = CesiumMath.clamp(
  215. maxBounds.y,
  216. defaultMinHeight,
  217. defaultMaxHeight
  218. );
  219. const clipMinHeight = CesiumMath.clamp(
  220. clipMinBounds.y,
  221. defaultMinHeight,
  222. defaultMaxHeight
  223. );
  224. const clipMaxHeight = CesiumMath.clamp(
  225. clipMaxBounds.y,
  226. defaultMinHeight,
  227. defaultMaxHeight
  228. );
  229. const renderMinHeight = Math.max(shapeMinHeight, clipMinHeight);
  230. const renderMaxHeight = Math.min(shapeMaxHeight, clipMaxHeight);
  231. // Clamp the angles to the valid range
  232. const shapeMinAngle = CesiumMath.negativePiToPi(minBounds.z);
  233. const shapeMaxAngle = CesiumMath.negativePiToPi(maxBounds.z);
  234. const clipMinAngle = CesiumMath.negativePiToPi(clipMinBounds.z);
  235. const clipMaxAngle = CesiumMath.negativePiToPi(clipMaxBounds.z);
  236. const renderMinAngle = Math.max(shapeMinAngle, clipMinAngle);
  237. const renderMaxAngle = Math.min(shapeMaxAngle, clipMaxAngle);
  238. const scale = Matrix4.getScale(modelMatrix, scratchScale);
  239. // Exit early if the shape is not visible.
  240. // Note that minAngle may be greater than maxAngle when crossing the 180th meridian.
  241. // Cylinder is not visible if:
  242. // - maxRadius is zero (line)
  243. // - minRadius is greater than maxRadius
  244. // - minHeight is greater than maxHeight
  245. // - scale is 0 for any component (too annoying to reconstruct rotation matrix)
  246. if (
  247. renderMaxRadius === 0.0 ||
  248. renderMinRadius > renderMaxRadius ||
  249. renderMinHeight > renderMaxHeight ||
  250. CesiumMath.equalsEpsilon(scale.x, 0.0, undefined, epsilonZeroScale) ||
  251. CesiumMath.equalsEpsilon(scale.y, 0.0, undefined, epsilonZeroScale) ||
  252. CesiumMath.equalsEpsilon(scale.z, 0.0, undefined, epsilonZeroScale)
  253. ) {
  254. return false;
  255. }
  256. this._minimumRadius = shapeMinRadius; // [0,1]
  257. this._maximumRadius = shapeMaxRadius; // [0,1]
  258. this._minimumHeight = shapeMinHeight; // [-1,+1]
  259. this._maximumHeight = shapeMaxHeight; // [-1,+1]
  260. this._minimumAngle = shapeMinAngle; // [-pi,+pi]
  261. this._maximumAngle = shapeMaxAngle; // [-pi,+pi]
  262. this.shapeTransform = Matrix4.clone(modelMatrix, this.shapeTransform);
  263. this.orientedBoundingBox = getCylinderChunkObb(
  264. renderMinRadius,
  265. renderMaxRadius,
  266. renderMinHeight,
  267. renderMaxHeight,
  268. renderMinAngle,
  269. renderMaxAngle,
  270. this.shapeTransform,
  271. this.orientedBoundingBox
  272. );
  273. this.boundTransform = Matrix4.fromRotationTranslation(
  274. this.orientedBoundingBox.halfAxes,
  275. this.orientedBoundingBox.center,
  276. this.boundTransform
  277. );
  278. this.boundingSphere = BoundingSphere.fromOrientedBoundingBox(
  279. this.orientedBoundingBox,
  280. this.boundingSphere
  281. );
  282. const shapeIsDefaultMaxRadius = shapeMaxRadius === defaultMaxRadius;
  283. const shapeIsDefaultMinRadius = shapeMinRadius === defaultMinRadius;
  284. const shapeIsDefaultRadius =
  285. shapeIsDefaultMinRadius && shapeIsDefaultMaxRadius;
  286. const shapeIsDefaultHeight =
  287. shapeMinHeight === defaultMinHeight && shapeMaxHeight === defaultMaxHeight;
  288. const shapeIsAngleReversed = shapeMaxAngle < shapeMinAngle;
  289. const shapeAngleRange =
  290. shapeMaxAngle - shapeMinAngle + shapeIsAngleReversed * defaultAngleRange;
  291. const shapeIsAngleRegular =
  292. shapeAngleRange > defaultAngleRangeHalf + epsilonAngle &&
  293. shapeAngleRange < defaultAngleRange - epsilonAngle;
  294. const shapeIsAngleFlipped =
  295. shapeAngleRange > epsilonAngle &&
  296. shapeAngleRange < defaultAngleRangeHalf - epsilonAngle;
  297. const shapeIsAngleRangeHalf =
  298. shapeAngleRange >= defaultAngleRangeHalf - epsilonAngle &&
  299. shapeAngleRange <= defaultAngleRangeHalf + epsilonAngle;
  300. const shapeIsAngleRangeZero = shapeAngleRange <= epsilonAngle;
  301. const shapeHasAngle =
  302. shapeIsAngleRegular ||
  303. shapeIsAngleFlipped ||
  304. shapeIsAngleRangeHalf ||
  305. shapeIsAngleRangeZero;
  306. const shapeIsMinAngleDiscontinuity = CesiumMath.equalsEpsilon(
  307. shapeMinAngle,
  308. defaultMinAngle,
  309. undefined,
  310. epsilonAngleDiscontinuity
  311. );
  312. const shapeIsMaxAngleDiscontinuity = CesiumMath.equalsEpsilon(
  313. shapeMaxAngle,
  314. defaultMaxAngle,
  315. undefined,
  316. epsilonAngleDiscontinuity
  317. );
  318. const renderIsDefaultMaxRadius = renderMaxRadius === defaultMaxRadius;
  319. const renderIsDefaultMinRadius = renderMinRadius === defaultMinRadius;
  320. const renderIsDefaultHeight =
  321. renderMinHeight === defaultMinHeight &&
  322. renderMaxHeight === defaultMaxHeight;
  323. const renderIsAngleReversed = renderMaxAngle < renderMinAngle;
  324. const renderAngleRange =
  325. renderMaxAngle - renderMinAngle + renderIsAngleReversed * defaultAngleRange;
  326. const renderIsAngleRegular =
  327. renderAngleRange > defaultAngleRangeHalf + epsilonAngle &&
  328. renderAngleRange < defaultAngleRange - epsilonAngle;
  329. const renderIsAngleFlipped =
  330. renderAngleRange > epsilonAngle &&
  331. renderAngleRange < defaultAngleRangeHalf - epsilonAngle;
  332. const renderIsAngleRangeHalf =
  333. renderAngleRange >= defaultAngleRangeHalf - epsilonAngle &&
  334. renderAngleRange <= defaultAngleRangeHalf + epsilonAngle;
  335. const renderIsAngleRangeZero = renderAngleRange <= epsilonAngle;
  336. const renderHasAngle =
  337. renderIsAngleRegular ||
  338. renderIsAngleFlipped ||
  339. renderIsAngleRangeHalf ||
  340. renderIsAngleRangeZero;
  341. const shaderUniforms = this.shaderUniforms;
  342. const shaderDefines = this.shaderDefines;
  343. // To keep things simple, clear the defines every time
  344. for (const key in shaderDefines) {
  345. if (shaderDefines.hasOwnProperty(key)) {
  346. shaderDefines[key] = undefined;
  347. }
  348. }
  349. // Keep track of how many intersections there are going to be.
  350. let intersectionCount = 0;
  351. shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MAX"] = intersectionCount;
  352. intersectionCount += 1;
  353. if (!renderIsDefaultMinRadius) {
  354. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MIN"] = true;
  355. shaderDefines["CYLINDER_INTERSECTION_INDEX_RADIUS_MIN"] = intersectionCount;
  356. intersectionCount += 1;
  357. shaderUniforms.cylinderUvToRenderRadiusMin =
  358. renderMaxRadius / renderMinRadius;
  359. }
  360. if (!renderIsDefaultMaxRadius) {
  361. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_MAX"] = true;
  362. }
  363. if (renderMinRadius === renderMaxRadius) {
  364. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_RADIUS_FLAT"] = true;
  365. }
  366. if (!renderIsDefaultHeight) {
  367. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT"] = true;
  368. }
  369. if (renderMinHeight === renderMaxHeight) {
  370. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_HEIGHT_FLAT"] = true;
  371. }
  372. if (shapeMinHeight === shapeMaxHeight) {
  373. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT_FLAT"] = true;
  374. }
  375. if (shapeMinRadius === shapeMaxRadius) {
  376. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS_FLAT"] = true;
  377. }
  378. if (!shapeIsDefaultRadius) {
  379. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_RADIUS"] = true;
  380. // delerp(radius, minRadius, maxRadius)
  381. // (radius - minRadius) / (maxRadius - minRadius)
  382. // radius / (maxRadius - minRadius) - minRadius / (maxRadius - minRadius)
  383. // scale = 1.0 / (maxRadius - minRadius)
  384. // offset = -minRadius / (maxRadius - minRadius)
  385. // offset = minRadius / (minRadius - maxRadius)
  386. const scale = 1.0 / (shapeMaxRadius - shapeMinRadius);
  387. const offset = shapeMinRadius / (shapeMinRadius - shapeMaxRadius);
  388. shaderUniforms.cylinderUvToShapeUvRadius = Cartesian2.fromElements(
  389. scale,
  390. offset,
  391. shaderUniforms.cylinderUvToShapeUvRadius
  392. );
  393. }
  394. if (!shapeIsDefaultHeight) {
  395. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_HEIGHT"] = true;
  396. // delerp(heightUv, minHeightUv, maxHeightUv)
  397. // (heightUv - minHeightUv) / (maxHeightUv - minHeightUv)
  398. // heightUv / (maxHeightUv - minHeightUv) - minHeightUv / (maxHeightUv - minHeightUv)
  399. // scale = 1.0 / (maxHeightUv - minHeightUv)
  400. // scale = 1.0 / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
  401. // scale = 2.0 / (maxHeight - minHeight)
  402. // offset = -minHeightUv / (maxHeightUv - minHeightUv)
  403. // offset = -minHeightUv / ((maxHeight * 0.5 + 0.5) - (minHeight * 0.5 + 0.5))
  404. // offset = -2.0 * (minHeight * 0.5 + 0.5) / (maxHeight - minHeight)
  405. // offset = -(minHeight + 1.0) / (maxHeight - minHeight)
  406. // offset = (minHeight + 1.0) / (minHeight - maxHeight)
  407. const scale = 2.0 / (shapeMaxHeight - shapeMinHeight);
  408. const offset = (shapeMinHeight + 1.0) / (shapeMinHeight - shapeMaxHeight);
  409. shaderUniforms.cylinderUvToShapeUvHeight = Cartesian2.fromElements(
  410. scale,
  411. offset,
  412. shaderUniforms.cylinderUvToShapeUvHeight
  413. );
  414. }
  415. if (!renderIsDefaultMaxRadius || !renderIsDefaultHeight) {
  416. const heightScale = 0.5 * (renderMaxHeight - renderMinHeight);
  417. const scaleLocalToBounds = Cartesian3.fromElements(
  418. 1.0 / renderMaxRadius,
  419. 1.0 / renderMaxRadius,
  420. 1.0 / (heightScale === 0.0 ? 1.0 : heightScale),
  421. scratchBoundsScale
  422. );
  423. // -inverse(scale) * translation // affine inverse
  424. // -inverse(scale) * 0.5 * (minHeight + maxHeight)
  425. const translateLocalToBounds = Cartesian3.fromElements(
  426. 0.0,
  427. 0.0,
  428. -scaleLocalToBounds.z * 0.5 * (renderMinHeight + renderMaxHeight),
  429. scratchBoundsTranslation
  430. );
  431. const transformLocalToBounds = Matrix4.fromRotationTranslation(
  432. Matrix3.fromScale(scaleLocalToBounds, scratchBoundsScaleMatrix),
  433. translateLocalToBounds,
  434. scratchTransformLocalToBounds
  435. );
  436. const transformUvToBounds = Matrix4.multiplyTransformation(
  437. transformLocalToBounds,
  438. transformUvToLocal,
  439. scratchTransformUvToBounds
  440. );
  441. shaderUniforms.cylinderUvToRenderBoundsScale = Matrix4.getScale(
  442. transformUvToBounds,
  443. shaderUniforms.cylinderUvToRenderBoundsScale
  444. );
  445. shaderUniforms.cylinderUvToRenderBoundsTranslate = Matrix4.getTranslation(
  446. transformUvToBounds,
  447. shaderUniforms.cylinderUvToRenderBoundsTranslate
  448. );
  449. }
  450. if (shapeIsAngleReversed) {
  451. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_MAX_REVERSED"] = true;
  452. }
  453. if (renderHasAngle) {
  454. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE"] = true;
  455. shaderDefines["CYLINDER_INTERSECTION_INDEX_ANGLE"] = intersectionCount;
  456. if (renderIsAngleRegular) {
  457. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_UNDER_HALF"] = true;
  458. intersectionCount += 1;
  459. } else if (renderIsAngleFlipped) {
  460. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_OVER_HALF"] = true;
  461. intersectionCount += 2;
  462. } else if (renderIsAngleRangeHalf) {
  463. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_HALF"] = true;
  464. intersectionCount += 1;
  465. } else if (renderIsAngleRangeZero) {
  466. shaderDefines["CYLINDER_HAS_RENDER_BOUNDS_ANGLE_RANGE_EQUAL_ZERO"] = true;
  467. intersectionCount += 2;
  468. }
  469. shaderUniforms.cylinderRenderAngleMinMax = Cartesian2.fromElements(
  470. renderMinAngle,
  471. renderMaxAngle,
  472. shaderUniforms.cylinderAngleMinMax
  473. );
  474. }
  475. if (shapeHasAngle) {
  476. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE"] = true;
  477. if (shapeIsAngleRangeZero) {
  478. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_RANGE_EQUAL_ZERO"] = true;
  479. }
  480. if (shapeIsMinAngleDiscontinuity) {
  481. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MIN_DISCONTINUITY"] = true;
  482. }
  483. if (shapeIsMaxAngleDiscontinuity) {
  484. shaderDefines["CYLINDER_HAS_SHAPE_BOUNDS_ANGLE_MAX_DISCONTINUITY"] = true;
  485. }
  486. const uvMinAngle = (shapeMinAngle - defaultMinAngle) / defaultAngleRange;
  487. const uvMaxAngle = (shapeMaxAngle - defaultMinAngle) / defaultAngleRange;
  488. const uvAngleRangeZero = 1.0 - shapeAngleRange / defaultAngleRange;
  489. shaderUniforms.cylinderShapeUvAngleMinMax = Cartesian2.fromElements(
  490. uvMinAngle,
  491. uvMaxAngle,
  492. shaderUniforms.cylinderShapeUvAngleMinMax
  493. );
  494. shaderUniforms.cylinderShapeUvAngleRangeZeroMid =
  495. (uvMaxAngle + 0.5 * uvAngleRangeZero) % 1.0;
  496. // delerp(angleUv, uvMinAngle, uvMaxAngle)
  497. // (angelUv - uvMinAngle) / (uvMaxAngle - uvMinAngle)
  498. // angleUv / (uvMaxAngle - uvMinAngle) - uvMinAngle / (uvMaxAngle - uvMinAngle)
  499. // scale = 1.0 / (uvMaxAngle - uvMinAngle)
  500. // scale = 1.0 / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
  501. // scale = 2.0 * pi / (maxAngle - minAngle)
  502. // offset = -uvMinAngle / (uvMaxAngle - uvMinAngle)
  503. // offset = -((minAngle - pi) / (2.0 * pi)) / (((maxAngle - pi) / (2.0 * pi)) - ((minAngle - pi) / (2.0 * pi)))
  504. // offset = -(minAngle - pi) / (maxAngle - minAngle)
  505. const scale = defaultAngleRange / shapeAngleRange;
  506. const offset = -(shapeMinAngle - defaultMinAngle) / shapeAngleRange;
  507. shaderUniforms.cylinderUvToShapeUvAngle = Cartesian2.fromElements(
  508. scale,
  509. offset,
  510. shaderUniforms.cylinderUvToShapeUvAngle
  511. );
  512. }
  513. this.shaderMaximumIntersectionsLength = intersectionCount;
  514. return true;
  515. };
  516. /**
  517. * Computes an oriented bounding box for a specified tile.
  518. * The update function must be called before calling this function.
  519. *
  520. * @param {number} tileLevel The tile's level.
  521. * @param {number} tileX The tile's x coordinate.
  522. * @param {number} tileY The tile's y coordinate.
  523. * @param {number} tileZ The tile's z coordinate.
  524. * @param {OrientedBoundingBox} result The oriented bounding box that will be set to enclose the specified tile
  525. * @returns {OrientedBoundingBox} The oriented bounding box.
  526. */
  527. VoxelCylinderShape.prototype.computeOrientedBoundingBoxForTile = function (
  528. tileLevel,
  529. tileX,
  530. tileY,
  531. tileZ,
  532. result
  533. ) {
  534. //>>includeStart('debug', pragmas.debug);
  535. Check.typeOf.number("tileLevel", tileLevel);
  536. Check.typeOf.number("tileX", tileX);
  537. Check.typeOf.number("tileY", tileY);
  538. Check.typeOf.number("tileZ", tileZ);
  539. Check.typeOf.object("result", result);
  540. //>>includeEnd('debug');
  541. const minimumRadius = this._minimumRadius;
  542. const maximumRadius = this._maximumRadius;
  543. const minimumHeight = this._minimumHeight;
  544. const maximumHeight = this._maximumHeight;
  545. const minimumAngle = this._minimumAngle;
  546. const maximumAngle = this._maximumAngle;
  547. const sizeAtLevel = 1.0 / Math.pow(2.0, tileLevel);
  548. const radiusStart = CesiumMath.lerp(
  549. minimumRadius,
  550. maximumRadius,
  551. tileX * sizeAtLevel
  552. );
  553. const radiusEnd = CesiumMath.lerp(
  554. minimumRadius,
  555. maximumRadius,
  556. (tileX + 1) * sizeAtLevel
  557. );
  558. const heightStart = CesiumMath.lerp(
  559. minimumHeight,
  560. maximumHeight,
  561. tileY * sizeAtLevel
  562. );
  563. const heightEnd = CesiumMath.lerp(
  564. minimumHeight,
  565. maximumHeight,
  566. (tileY + 1) * sizeAtLevel
  567. );
  568. const angleStart = CesiumMath.lerp(
  569. minimumAngle,
  570. maximumAngle,
  571. tileZ * sizeAtLevel
  572. );
  573. const angleEnd = CesiumMath.lerp(
  574. minimumAngle,
  575. maximumAngle,
  576. (tileZ + 1) * sizeAtLevel
  577. );
  578. return getCylinderChunkObb(
  579. radiusStart,
  580. radiusEnd,
  581. heightStart,
  582. heightEnd,
  583. angleStart,
  584. angleEnd,
  585. this.shapeTransform,
  586. result
  587. );
  588. };
  589. const scratchOrientedBoundingBox = new OrientedBoundingBox();
  590. const scratchVoxelScale = new Cartesian3();
  591. const scratchRootScale = new Cartesian3();
  592. const scratchScaleRatio = new Cartesian3();
  593. /**
  594. * Computes an approximate step size for raymarching the root tile of a voxel grid.
  595. * The update function must be called before calling this function.
  596. *
  597. * @param {Cartesian3} dimensions The voxel grid dimensions for a tile.
  598. * @returns {number} The step size.
  599. */
  600. VoxelCylinderShape.prototype.computeApproximateStepSize = function (
  601. dimensions
  602. ) {
  603. //>>includeStart('debug', pragmas.debug);
  604. Check.typeOf.object("dimensions", dimensions);
  605. //>>includeEnd('debug');
  606. const shapeTransform = this.shapeTransform;
  607. const minRadius = this._minimumRadius;
  608. const maxRadius = this._maximumRadius;
  609. const minHeight = this._minimumHeight;
  610. const maxHeight = this._maximumHeight;
  611. const minAngle = this._minimumAngle;
  612. const maxAngle = this._maximumAngle;
  613. const lerpRadius = 1.0 - 1.0 / dimensions.x;
  614. const lerpHeight = 1.0 - 1.0 / dimensions.y;
  615. const lerpAngle = 1.0 - 1.0 / dimensions.z;
  616. // Compare the size of an outermost cylinder voxel to the total cylinder
  617. const voxelMinimumRadius = CesiumMath.lerp(minRadius, maxRadius, lerpRadius);
  618. const voxelMinimumHeight = CesiumMath.lerp(minHeight, maxHeight, lerpHeight);
  619. const voxelMinimumAngle = CesiumMath.lerp(minAngle, maxAngle, lerpAngle);
  620. const voxelMaximumRadius = maxRadius;
  621. const voxelMaximumHeight = maxHeight;
  622. const voxelMaximumAngle = maxAngle;
  623. const voxelObb = getCylinderChunkObb(
  624. voxelMinimumRadius,
  625. voxelMaximumRadius,
  626. voxelMinimumHeight,
  627. voxelMaximumHeight,
  628. voxelMinimumAngle,
  629. voxelMaximumAngle,
  630. shapeTransform,
  631. scratchOrientedBoundingBox
  632. );
  633. const voxelScale = Matrix3.getScale(voxelObb.halfAxes, scratchVoxelScale);
  634. const rootScale = Matrix4.getScale(shapeTransform, scratchRootScale);
  635. const scaleRatio = Cartesian3.divideComponents(
  636. voxelScale,
  637. rootScale,
  638. scratchScaleRatio
  639. );
  640. const stepSize = Cartesian3.minimumComponent(scaleRatio);
  641. return stepSize;
  642. };
  643. /**
  644. * Defines the minimum bounds of the shape. Corresponds to minimum radius, height, angle.
  645. *
  646. * @type {Cartesian3}
  647. * @constant
  648. * @readonly
  649. *
  650. * @private
  651. */
  652. VoxelCylinderShape.DefaultMinBounds = Object.freeze(
  653. new Cartesian3(0.0, -1.0, -CesiumMath.PI)
  654. );
  655. /**
  656. * Defines the maximum bounds of the shape. Corresponds to maximum radius, height, angle.
  657. *
  658. * @type {Cartesian3}
  659. * @constant
  660. * @readonly
  661. *
  662. * @private
  663. */
  664. VoxelCylinderShape.DefaultMaxBounds = Object.freeze(
  665. new Cartesian3(1.0, +1.0, +CesiumMath.PI)
  666. );
  667. const maxTestAngles = 5;
  668. const scratchTestAngles = new Array(maxTestAngles);
  669. const scratchTranslation = new Cartesian3();
  670. const scratchRotation = new Matrix3();
  671. const scratchTranslationMatrix = new Matrix4();
  672. const scratchRotationMatrix = new Matrix4();
  673. const scratchScaleMatrix = new Matrix4();
  674. const scratchMatrix = new Matrix4();
  675. const scratchColumn0 = new Cartesian3();
  676. const scratchColumn1 = new Cartesian3();
  677. const scratchColumn2 = new Cartesian3();
  678. const scratchCorners = new Array(8);
  679. for (let i = 0; i < 8; i++) {
  680. scratchCorners[i] = new Cartesian3();
  681. }
  682. function orthogonal(a, b, epsilon) {
  683. return Math.abs(Cartesian4.dot(a, b)) < epsilon;
  684. }
  685. function isValidOrientedBoundingBoxTransformation(matrix) {
  686. const column0 = Matrix4.getColumn(matrix, 0, scratchColumn0);
  687. const column1 = Matrix4.getColumn(matrix, 1, scratchColumn1);
  688. const column2 = Matrix4.getColumn(matrix, 2, scratchColumn2);
  689. const epsilon = CesiumMath.EPSILON4;
  690. return (
  691. orthogonal(column0, column1, epsilon) &&
  692. orthogonal(column1, column2, epsilon)
  693. );
  694. }
  695. function computeLooseOrientedBoundingBox(matrix, result) {
  696. const corners = scratchCorners;
  697. Cartesian3.fromElements(-0.5, -0.5, -0.5, corners[0]);
  698. Cartesian3.fromElements(-0.5, -0.5, 0.5, corners[1]);
  699. Cartesian3.fromElements(-0.5, 0.5, -0.5, corners[2]);
  700. Cartesian3.fromElements(-0.5, 0.5, 0.5, corners[3]);
  701. Cartesian3.fromElements(0.5, -0.5, -0.5, corners[4]);
  702. Cartesian3.fromElements(0.5, -0.5, 0.5, corners[5]);
  703. Cartesian3.fromElements(0.5, 0.5, -0.5, corners[6]);
  704. Cartesian3.fromElements(0.5, 0.5, 0.5, corners[7]);
  705. for (let i = 0; i < 8; ++i) {
  706. Matrix4.multiplyByPoint(matrix, corners[i], corners[i]);
  707. }
  708. return OrientedBoundingBox.fromPoints(corners, result);
  709. }
  710. /**
  711. * Computes an {@link OrientedBoundingBox} for a subregion of the shape.
  712. *
  713. * @function
  714. *
  715. * @param {number} radiusStart The radiusStart.
  716. * @param {number} radiusEnd The radiusEnd.
  717. * @param {number} heightStart The heightStart.
  718. * @param {number} heightEnd The heightEnd.
  719. * @param {number} angleStart The angleStart.
  720. * @param {number} angleEnd The angleEnd.
  721. * @param {Matrix4} matrix The matrix to transform the points.
  722. * @param {OrientedBoundingBox} result The object onto which to store the result.
  723. * @returns {OrientedBoundingBox} The oriented bounding box that contains this subregion.
  724. *
  725. * @private
  726. */
  727. function getCylinderChunkObb(
  728. radiusStart,
  729. radiusEnd,
  730. heightStart,
  731. heightEnd,
  732. angleStart,
  733. angleEnd,
  734. matrix,
  735. result
  736. ) {
  737. const defaultMinBounds = VoxelCylinderShape.DefaultMinBounds;
  738. const defaultMaxBounds = VoxelCylinderShape.DefaultMaxBounds;
  739. const defaultMinRadius = defaultMinBounds.x; // 0
  740. const defaultMaxRadius = defaultMaxBounds.x; // 1
  741. const defaultMinHeight = defaultMinBounds.y; // -1
  742. const defaultMaxHeight = defaultMaxBounds.y; // +1
  743. const defaultMinAngle = defaultMinBounds.z; // -pi
  744. const defaultMaxAngle = defaultMaxBounds.z; // +pi
  745. // Return early if using the default bounds
  746. if (
  747. radiusStart === defaultMinRadius &&
  748. radiusEnd === defaultMaxRadius &&
  749. heightStart === defaultMinHeight &&
  750. heightEnd === defaultMaxHeight &&
  751. angleStart === defaultMinAngle &&
  752. angleEnd === defaultMaxAngle
  753. ) {
  754. result.center = Matrix4.getTranslation(matrix, result.center);
  755. result.halfAxes = Matrix4.getMatrix3(matrix, result.halfAxes);
  756. return result;
  757. }
  758. const isAngleReversed = angleEnd < angleStart;
  759. if (isAngleReversed) {
  760. angleEnd += CesiumMath.TWO_PI;
  761. }
  762. const angleRange = angleEnd - angleStart;
  763. const angleMid = angleStart + angleRange * 0.5;
  764. const testAngles = scratchTestAngles;
  765. let testAngleCount = 0;
  766. testAngles[testAngleCount++] = angleStart;
  767. testAngles[testAngleCount++] = angleEnd;
  768. testAngles[testAngleCount++] = angleMid;
  769. if (angleRange > CesiumMath.PI) {
  770. testAngles[testAngleCount++] = angleMid - CesiumMath.PI_OVER_TWO;
  771. testAngles[testAngleCount++] = angleMid + CesiumMath.PI_OVER_TWO;
  772. }
  773. // Find bounding box in shape space relative to angleMid
  774. let minX = 1.0;
  775. let minY = 1.0;
  776. let maxX = -1.0;
  777. let maxY = -1.0;
  778. for (let i = 0; i < testAngleCount; ++i) {
  779. const angle = testAngles[i] - angleMid;
  780. const cosAngle = Math.cos(angle);
  781. const sinAngle = Math.sin(angle);
  782. const x1 = cosAngle * radiusStart;
  783. const y1 = sinAngle * radiusStart;
  784. const x2 = cosAngle * radiusEnd;
  785. const y2 = sinAngle * radiusEnd;
  786. minX = Math.min(minX, x1);
  787. minY = Math.min(minY, y1);
  788. minX = Math.min(minX, x2);
  789. minY = Math.min(minY, y2);
  790. maxX = Math.max(maxX, x1);
  791. maxY = Math.max(maxY, y1);
  792. maxX = Math.max(maxX, x2);
  793. maxY = Math.max(maxY, y2);
  794. }
  795. const extentX = maxX - minX;
  796. const extentY = maxY - minY;
  797. const extentZ = heightEnd - heightStart;
  798. const centerX = (minX + maxX) * 0.5;
  799. const centerY = (minY + maxY) * 0.5;
  800. const centerZ = (heightStart + heightEnd) * 0.5;
  801. const translation = Cartesian3.fromElements(
  802. centerX,
  803. centerY,
  804. centerZ,
  805. scratchTranslation
  806. );
  807. const rotation = Matrix3.fromRotationZ(angleMid, scratchRotation);
  808. const scale = Cartesian3.fromElements(
  809. extentX,
  810. extentY,
  811. extentZ,
  812. scratchScale
  813. );
  814. const scaleMatrix = Matrix4.fromScale(scale, scratchScaleMatrix);
  815. const rotationMatrix = Matrix4.fromRotation(rotation, scratchRotationMatrix);
  816. const translationMatrix = Matrix4.fromTranslation(
  817. translation,
  818. scratchTranslationMatrix
  819. );
  820. // Shape space matrix = R * T * S
  821. const localMatrix = Matrix4.multiplyTransformation(
  822. rotationMatrix,
  823. Matrix4.multiplyTransformation(
  824. translationMatrix,
  825. scaleMatrix,
  826. scratchMatrix
  827. ),
  828. scratchMatrix
  829. );
  830. const globalMatrix = Matrix4.multiplyTransformation(
  831. matrix,
  832. localMatrix,
  833. scratchMatrix
  834. );
  835. if (!isValidOrientedBoundingBoxTransformation(globalMatrix)) {
  836. return computeLooseOrientedBoundingBox(globalMatrix, result);
  837. }
  838. return OrientedBoundingBox.fromTransformation(globalMatrix, result);
  839. }
  840. export default VoxelCylinderShape;