Math3D.js 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /* *
  2. *
  3. * (c) 2010-2021 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import H from '../Core/Globals.js';
  12. import U from '../Core/Utilities.js';
  13. var pick = U.pick;
  14. // Mathematical Functionility
  15. var deg2rad = H.deg2rad;
  16. /* eslint-disable max-len */
  17. /**
  18. * Apply 3-D rotation
  19. * Euler Angles (XYZ):
  20. * cosA = cos(Alfa|Roll)
  21. * cosB = cos(Beta|Pitch)
  22. * cosG = cos(Gamma|Yaw)
  23. *
  24. * Composite rotation:
  25. * | cosB * cosG | cosB * sinG | -sinB |
  26. * | sinA * sinB * cosG - cosA * sinG | sinA * sinB * sinG + cosA * cosG | sinA * cosB |
  27. * | cosA * sinB * cosG + sinA * sinG | cosA * sinB * sinG - sinA * cosG | cosA * cosB |
  28. *
  29. * Now, Gamma/Yaw is not used (angle=0), so we assume cosG = 1 and sinG = 0, so
  30. * we get:
  31. * | cosB | 0 | - sinB |
  32. * | sinA * sinB | cosA | sinA * cosB |
  33. * | cosA * sinB | - sinA | cosA * cosB |
  34. *
  35. * But in browsers, y is reversed, so we get sinA => -sinA. The general result
  36. * is:
  37. * | cosB | 0 | - sinB | | x | | px |
  38. * | - sinA * sinB | cosA | - sinA * cosB | x | y | = | py |
  39. * | cosA * sinB | sinA | cosA * cosB | | z | | pz |
  40. *
  41. * @private
  42. * @function rotate3D
  43. */
  44. /* eslint-enable max-len */
  45. /**
  46. * @private
  47. * @param {number} x
  48. * X coordinate
  49. * @param {number} y
  50. * Y coordinate
  51. * @param {number} z
  52. * Z coordinate
  53. * @param {Highcharts.Rotation3dObject} angles
  54. * Rotation angles
  55. * @return {Highcharts.Rotation3dObject}
  56. * Rotated position
  57. */
  58. function rotate3D(x, y, z, angles) {
  59. return {
  60. x: angles.cosB * x - angles.sinB * z,
  61. y: -angles.sinA * angles.sinB * x + angles.cosA * y -
  62. angles.cosB * angles.sinA * z,
  63. z: angles.cosA * angles.sinB * x + angles.sinA * y +
  64. angles.cosA * angles.cosB * z
  65. };
  66. }
  67. /**
  68. * Perspective3D function is available in global Highcharts scope because is
  69. * needed also outside of perspective() function (#8042).
  70. * @private
  71. * @function Highcharts.perspective3D
  72. *
  73. * @param {Highcharts.Position3DObject} coordinate
  74. * 3D position
  75. *
  76. * @param {Highcharts.Position3DObject} origin
  77. * 3D root position
  78. *
  79. * @param {number} distance
  80. * Perspective distance
  81. *
  82. * @return {Highcharts.PositionObject}
  83. * Perspective 3D Position
  84. *
  85. * @requires highcharts-3d
  86. */
  87. function perspective3D(coordinate, origin, distance) {
  88. var projection = ((distance > 0) && (distance < Number.POSITIVE_INFINITY)) ?
  89. distance / (coordinate.z + origin.z + distance) :
  90. 1;
  91. return {
  92. x: coordinate.x * projection,
  93. y: coordinate.y * projection
  94. };
  95. }
  96. H.perspective3D = perspective3D;
  97. /**
  98. * Transforms a given array of points according to the angles in chart.options.
  99. *
  100. * @private
  101. * @function Highcharts.perspective
  102. *
  103. * @param {Array<Highcharts.Position3DObject>} points
  104. * The array of points
  105. *
  106. * @param {Highcharts.Chart} chart
  107. * The chart
  108. *
  109. * @param {boolean} [insidePlotArea]
  110. * Whether to verify that the points are inside the plotArea
  111. *
  112. * @param {boolean} [useInvertedPersp]
  113. * Whether to use inverted perspective in calculations
  114. *
  115. * @return {Array<Highcharts.Position3DObject>}
  116. * An array of transformed points
  117. *
  118. * @requires highcharts-3d
  119. */
  120. function perspective(points, chart, insidePlotArea, useInvertedPersp) {
  121. var options3d = chart.options.chart.options3d,
  122. /* The useInvertedPersp argument is used for
  123. * inverted charts with already inverted elements,
  124. * such as dataLabels or tooltip positions.
  125. */
  126. inverted = pick(useInvertedPersp, insidePlotArea ? chart.inverted : false), origin = {
  127. x: chart.plotWidth / 2,
  128. y: chart.plotHeight / 2,
  129. z: options3d.depth / 2,
  130. vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
  131. }, scale = chart.scale3d || 1, beta = deg2rad * options3d.beta * (inverted ? -1 : 1), alpha = deg2rad * options3d.alpha * (inverted ? -1 : 1), angles = {
  132. cosA: Math.cos(alpha),
  133. cosB: Math.cos(-beta),
  134. sinA: Math.sin(alpha),
  135. sinB: Math.sin(-beta)
  136. };
  137. if (!insidePlotArea) {
  138. origin.x += chart.plotLeft;
  139. origin.y += chart.plotTop;
  140. }
  141. // Transform each point
  142. return points.map(function (point) {
  143. var rotated = rotate3D((inverted ? point.y : point.x) - origin.x, (inverted ? point.x : point.y) - origin.y, (point.z || 0) - origin.z, angles),
  144. // Apply perspective
  145. coordinate = perspective3D(rotated, origin, origin.vd);
  146. // Apply translation
  147. coordinate.x = coordinate.x * scale + origin.x;
  148. coordinate.y = coordinate.y * scale + origin.y;
  149. coordinate.z = rotated.z * scale + origin.z;
  150. return {
  151. x: (inverted ? coordinate.y : coordinate.x),
  152. y: (inverted ? coordinate.x : coordinate.y),
  153. z: coordinate.z
  154. };
  155. });
  156. }
  157. H.perspective = perspective;
  158. /**
  159. * Calculate a distance from camera to points - made for calculating zIndex of
  160. * scatter points.
  161. *
  162. * @private
  163. * @function Highcharts.pointCameraDistance
  164. *
  165. * @param {Highcharts.Dictionary<number>} coordinates
  166. * Coordinates of the specific point
  167. *
  168. * @param {Highcharts.Chart} chart
  169. * Related chart
  170. *
  171. * @return {number}
  172. * Distance from camera to point
  173. *
  174. * @requires highcharts-3d
  175. */
  176. function pointCameraDistance(coordinates, chart) {
  177. var options3d = chart.options.chart.options3d, cameraPosition = {
  178. x: chart.plotWidth / 2,
  179. y: chart.plotHeight / 2,
  180. z: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0) +
  181. options3d.depth
  182. },
  183. // Added support for objects with plotX or x coordinates.
  184. distance = Math.sqrt(Math.pow(cameraPosition.x - pick(coordinates.plotX, coordinates.x), 2) +
  185. Math.pow(cameraPosition.y - pick(coordinates.plotY, coordinates.y), 2) +
  186. Math.pow(cameraPosition.z - pick(coordinates.plotZ, coordinates.z), 2));
  187. return distance;
  188. }
  189. H.pointCameraDistance = pointCameraDistance;
  190. /**
  191. * Calculate area of a 2D polygon using Shoelace algorithm
  192. * https://en.wikipedia.org/wiki/Shoelace_formula
  193. *
  194. * @private
  195. * @function Highcharts.shapeArea
  196. *
  197. * @param {Array<Highcharts.PositionObject>} vertexes
  198. * 2D Polygon
  199. *
  200. * @return {number}
  201. * Calculated area
  202. *
  203. * @requires highcharts-3d
  204. */
  205. function shapeArea(vertexes) {
  206. var area = 0, i, j;
  207. for (i = 0; i < vertexes.length; i++) {
  208. j = (i + 1) % vertexes.length;
  209. area += vertexes[i].x * vertexes[j].y - vertexes[j].x * vertexes[i].y;
  210. }
  211. return area / 2;
  212. }
  213. H.shapeArea = shapeArea;
  214. /**
  215. * Calculate area of a 3D polygon after perspective projection
  216. *
  217. * @private
  218. * @function Highcharts.shapeArea3d
  219. *
  220. * @param {Array<Highcharts.Position3DObject>} vertexes
  221. * 3D Polygon
  222. *
  223. * @param {Highcharts.Chart} chart
  224. * Related chart
  225. *
  226. * @param {boolean} [insidePlotArea]
  227. * Whether to verify that the points are inside the plotArea
  228. *
  229. * @return {number}
  230. * Calculated area
  231. *
  232. * @requires highcharts-3d
  233. */
  234. function shapeArea3D(vertexes, chart, insidePlotArea) {
  235. return shapeArea(perspective(vertexes, chart, insidePlotArea));
  236. }
  237. H.shapeArea3d = shapeArea3D;
  238. var mathModule = {
  239. perspective: perspective,
  240. perspective3D: perspective3D,
  241. pointCameraDistance: pointCameraDistance,
  242. shapeArea: shapeArea,
  243. shapeArea3D: shapeArea3D
  244. };
  245. export default mathModule;