1
0

threejs-align-html-elements-to-3d.js 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. import { GUI } from '../../examples/jsm/libs/lil-gui.module.min.js';
  2. {
  3. function outlineText( ctx, msg, x, y ) {
  4. ctx.strokeText( msg, x, y );
  5. ctx.fillText( msg, x, y );
  6. }
  7. function arrow( ctx, x1, y1, x2, y2, start, end, size ) {
  8. size = size || 1;
  9. const dx = x1 - x2;
  10. const dy = y1 - y2;
  11. const rot = - Math.atan2( dx, dy );
  12. const len = Math.sqrt( dx * dx + dy * dy );
  13. ctx.save();
  14. {
  15. ctx.translate( x1, y1 );
  16. ctx.rotate( rot );
  17. ctx.beginPath();
  18. ctx.moveTo( 0, 0 );
  19. ctx.lineTo( 0, - ( len - 10 * size ) );
  20. ctx.stroke();
  21. }
  22. ctx.restore();
  23. if ( start ) {
  24. arrowHead( ctx, x1, y1, rot, size );
  25. }
  26. if ( end ) {
  27. arrowHead( ctx, x2, y2, rot + Math.PI, size );
  28. }
  29. }
  30. function arrowHead( ctx, x, y, rot, size ) {
  31. ctx.save();
  32. {
  33. ctx.translate( x, y );
  34. ctx.rotate( rot );
  35. ctx.scale( size, size );
  36. ctx.translate( 0, - 10 );
  37. ctx.beginPath();
  38. ctx.moveTo( 0, 0 );
  39. ctx.lineTo( - 5, - 2 );
  40. ctx.lineTo( 0, 10 );
  41. ctx.lineTo( 5, - 2 );
  42. ctx.closePath();
  43. ctx.fill();
  44. }
  45. ctx.restore();
  46. }
  47. const THREE = {
  48. MathUtils: {
  49. radToDeg( rad ) {
  50. return rad * 180 / Math.PI;
  51. },
  52. degToRad( deg ) {
  53. return deg * Math.PI / 180;
  54. },
  55. },
  56. };
  57. class DegRadHelper {
  58. constructor( obj, prop ) {
  59. this.obj = obj;
  60. this.prop = prop;
  61. }
  62. get value() {
  63. return THREE.MathUtils.radToDeg( this.obj[ this.prop ] );
  64. }
  65. set value( v ) {
  66. this.obj[ this.prop ] = THREE.MathUtils.degToRad( v );
  67. }
  68. }
  69. function dot( x1, y1, x2, y2 ) {
  70. return x1 * x2 + y1 * y2;
  71. }
  72. function distance( x1, y1, x2, y2 ) {
  73. const dx = x1 - x2;
  74. const dy = y1 - y2;
  75. return Math.sqrt( dx * dx + dy * dy );
  76. }
  77. function normalize( x, y ) {
  78. const l = distance( 0, 0, x, y );
  79. if ( l > 0.00001 ) {
  80. return [ x / l, y / l ];
  81. } else {
  82. return [ 0, 0 ];
  83. }
  84. }
  85. function resizeCanvasToDisplaySize( canvas, pixelRatio = 1 ) {
  86. const width = canvas.clientWidth * pixelRatio | 0;
  87. const height = canvas.clientHeight * pixelRatio | 0;
  88. const needResize = canvas.width !== width || canvas.height !== height;
  89. if ( needResize ) {
  90. canvas.width = width;
  91. canvas.height = height;
  92. }
  93. return needResize;
  94. }
  95. const diagrams = {
  96. dotProduct: {
  97. create( info ) {
  98. const { elem } = info;
  99. const div = document.createElement( 'div' );
  100. div.style.position = 'relative';
  101. div.style.width = '100%';
  102. div.style.height = '100%';
  103. elem.appendChild( div );
  104. const ctx = document.createElement( 'canvas' ).getContext( '2d' );
  105. div.appendChild( ctx.canvas );
  106. const settings = {
  107. rotation: 0.3,
  108. };
  109. const gui = new GUI( { autoPlace: false } );
  110. gui.add( new DegRadHelper( settings, 'rotation' ), 'value', - 180, 180 ).name( 'rotation' ).onChange( render );
  111. gui.domElement.style.position = 'absolute';
  112. gui.domElement.style.top = '0';
  113. gui.domElement.style.right = '0';
  114. div.appendChild( gui.domElement );
  115. const darkColors = {
  116. globe: 'green',
  117. camera: '#AAA',
  118. base: '#DDD',
  119. label: '#0FF',
  120. };
  121. const lightColors = {
  122. globe: '#0C0',
  123. camera: 'black',
  124. base: '#000',
  125. label: 'blue',
  126. };
  127. const darkMatcher = window.matchMedia( '(prefers-color-scheme: dark)' );
  128. darkMatcher.addEventListener( 'change', render );
  129. function render() {
  130. const { rotation } = settings;
  131. const isDarkMode = darkMatcher.matches;
  132. const colors = isDarkMode ? darkColors : lightColors;
  133. const pixelRatio = window.devicePixelRatio;
  134. resizeCanvasToDisplaySize( ctx.canvas, pixelRatio );
  135. ctx.clearRect( 0, 0, ctx.canvas.width, ctx.canvas.height );
  136. ctx.save();
  137. {
  138. const width = ctx.canvas.width / pixelRatio;
  139. const height = ctx.canvas.height / pixelRatio;
  140. const min = Math.min( width, height );
  141. const half = min / 2;
  142. const r = half * 0.4;
  143. const x = r * Math.sin( - rotation );
  144. const y = r * Math.cos( - rotation );
  145. const camDX = x - 0;
  146. const camDY = y - ( half - 40 );
  147. const labelDir = normalize( x, y );
  148. const camToLabelDir = normalize( camDX, camDY );
  149. const dp = dot( ...camToLabelDir, ...labelDir );
  150. ctx.scale( pixelRatio, pixelRatio );
  151. ctx.save();
  152. {
  153. {
  154. ctx.translate( width / 2, height / 2 );
  155. ctx.beginPath();
  156. ctx.arc( 0, 0, half * 0.4, 0, Math.PI * 2 );
  157. ctx.fillStyle = colors.globe;
  158. ctx.fill();
  159. ctx.save();
  160. {
  161. ctx.fillStyle = colors.camera;
  162. ctx.translate( 0, half );
  163. ctx.fillRect( - 15, - 30, 30, 30 );
  164. ctx.beginPath();
  165. ctx.moveTo( 0, - 25 );
  166. ctx.lineTo( - 25, - 50 );
  167. ctx.lineTo( 25, - 50 );
  168. ctx.closePath();
  169. ctx.fill();
  170. }
  171. ctx.restore();
  172. ctx.save();
  173. {
  174. ctx.lineWidth = 4;
  175. ctx.strokeStyle = colors.camera;
  176. ctx.fillStyle = colors.camera;
  177. arrow( ctx, 0, half - 40, x, y, false, true, 2 );
  178. ctx.save();
  179. {
  180. ctx.strokeStyle = colors.label;
  181. ctx.fillStyle = colors.label;
  182. arrow( ctx, 0, 0, x, y, false, true, 2 );
  183. }
  184. ctx.restore();
  185. {
  186. ctx.lineWidth = 3;
  187. ctx.strokeStyle = 'black';
  188. ctx.fillStyle = dp < 0 ? 'white' : 'red';
  189. ctx.font = '20px sans-serif';
  190. ctx.textAlign = 'center';
  191. ctx.textBaseline = 'middle';
  192. outlineText( ctx, 'label', x, y );
  193. }
  194. }
  195. ctx.restore();
  196. }
  197. ctx.restore();
  198. }
  199. ctx.lineWidth = 3;
  200. ctx.font = '24px sans-serif';
  201. ctx.strokeStyle = 'black';
  202. ctx.textAlign = 'left';
  203. ctx.textBaseline = 'middle';
  204. ctx.save();
  205. {
  206. ctx.translate( width / 4, 80 );
  207. const textColor = dp < 0 ? colors.base : 'red';
  208. advanceText( ctx, textColor, 'dot( ' );
  209. ctx.save();
  210. {
  211. ctx.fillStyle = colors.camera;
  212. ctx.strokeStyle = colors.camera;
  213. ctx.rotate( Math.atan2( camDY, camDX ) );
  214. arrow( ctx, - 8, 0, 8, 0, false, true, 1 );
  215. }
  216. ctx.restore();
  217. advanceText( ctx, textColor, ' , ' );
  218. ctx.save();
  219. {
  220. ctx.fillStyle = colors.label;
  221. ctx.strokeStyle = colors.label;
  222. ctx.rotate( rotation + Math.PI * 0.5 );
  223. arrow( ctx, - 8, 0, 8, 0, false, true, 1 );
  224. }
  225. ctx.restore();
  226. advanceText( ctx, textColor, ` ) = ${dp.toFixed( 2 )}` );
  227. }
  228. ctx.restore();
  229. }
  230. ctx.restore();
  231. }
  232. render();
  233. window.addEventListener( 'resize', render );
  234. },
  235. },
  236. };
  237. function advanceText( ctx, color, str ) {
  238. ctx.fillStyle = color;
  239. ctx.fillText( str, 0, 0 );
  240. ctx.translate( ctx.measureText( str ).width, 0 );
  241. }
  242. [ ...document.querySelectorAll( '[data-diagram]' ) ].forEach( createDiagram );
  243. function createDiagram( base ) {
  244. const name = base.dataset.diagram;
  245. const info = diagrams[ name ];
  246. if ( ! info ) {
  247. throw new Error( `no diagram ${name}` );
  248. }
  249. info.create( { elem: base } );
  250. }
  251. }