You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

320 lines
7.4 KiB

1 month ago
  1. /**
  2. * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
  3. * @author mrdoob / http://mrdoob.com/
  4. * @author yomotsu / https://yomotsu.net/
  5. */
  6. import * as THREE from "three";
  7. export class CSS3DObject extends THREE.Object3D {
  8. constructor(element) {
  9. super();
  10. this.element = element;
  11. this.element.style.position = "absolute";
  12. this.addEventListener("removed", function () {
  13. if (this.element.parentNode !== null) {
  14. this.element.parentNode.removeChild(this.element);
  15. }
  16. });
  17. }
  18. }
  19. export class CSS3DSprite extends CSS3DObject {
  20. constructor(element) {
  21. super(element);
  22. }
  23. }
  24. //
  25. export function CSS3DRenderer() {
  26. console.log("THREE.CSS3DRenderer", THREE.REVISION);
  27. var _width, _height;
  28. var _widthHalf, _heightHalf;
  29. var matrix = new THREE.Matrix4();
  30. var cache = {
  31. camera: { fov: 0, style: "" },
  32. objects: new WeakMap(),
  33. };
  34. var domElement = document.createElement("div");
  35. domElement.style.overflow = "hidden";
  36. this.domElement = domElement;
  37. var cameraElement = document.createElement("div");
  38. cameraElement.style.WebkitTransformStyle = "preserve-3d";
  39. cameraElement.style.transformStyle = "preserve-3d";
  40. domElement.appendChild(cameraElement);
  41. var isIE = /Trident/i.test(navigator.userAgent);
  42. this.getSize = function () {
  43. return {
  44. width: _width,
  45. height: _height,
  46. };
  47. };
  48. this.setSize = function (width, height) {
  49. _width = width;
  50. _height = height;
  51. _widthHalf = _width / 2;
  52. _heightHalf = _height / 2;
  53. domElement.style.width = width + "px";
  54. domElement.style.height = height + "px";
  55. cameraElement.style.width = width + "px";
  56. cameraElement.style.height = height + "px";
  57. };
  58. function epsilon(value) {
  59. return Math.abs(value) < 1e-10 ? 0 : value;
  60. }
  61. function getCameraCSSMatrix(matrix) {
  62. var elements = matrix.elements;
  63. return (
  64. "matrix3d(" +
  65. epsilon(elements[0]) +
  66. "," +
  67. epsilon(-elements[1]) +
  68. "," +
  69. epsilon(elements[2]) +
  70. "," +
  71. epsilon(elements[3]) +
  72. "," +
  73. epsilon(elements[4]) +
  74. "," +
  75. epsilon(-elements[5]) +
  76. "," +
  77. epsilon(elements[6]) +
  78. "," +
  79. epsilon(elements[7]) +
  80. "," +
  81. epsilon(elements[8]) +
  82. "," +
  83. epsilon(-elements[9]) +
  84. "," +
  85. epsilon(elements[10]) +
  86. "," +
  87. epsilon(elements[11]) +
  88. "," +
  89. epsilon(elements[12]) +
  90. "," +
  91. epsilon(-elements[13]) +
  92. "," +
  93. epsilon(elements[14]) +
  94. "," +
  95. epsilon(elements[15]) +
  96. ")"
  97. );
  98. }
  99. function getObjectCSSMatrix(matrix, cameraCSSMatrix) {
  100. var elements = matrix.elements;
  101. var matrix3d =
  102. "matrix3d(" +
  103. epsilon(elements[0]) +
  104. "," +
  105. epsilon(elements[1]) +
  106. "," +
  107. epsilon(elements[2]) +
  108. "," +
  109. epsilon(elements[3]) +
  110. "," +
  111. epsilon(-elements[4]) +
  112. "," +
  113. epsilon(-elements[5]) +
  114. "," +
  115. epsilon(-elements[6]) +
  116. "," +
  117. epsilon(-elements[7]) +
  118. "," +
  119. epsilon(elements[8]) +
  120. "," +
  121. epsilon(elements[9]) +
  122. "," +
  123. epsilon(elements[10]) +
  124. "," +
  125. epsilon(elements[11]) +
  126. "," +
  127. epsilon(elements[12]) +
  128. "," +
  129. epsilon(elements[13]) +
  130. "," +
  131. epsilon(elements[14]) +
  132. "," +
  133. epsilon(elements[15]) +
  134. ")";
  135. if (isIE) {
  136. return (
  137. "translate(-50%,-50%)" +
  138. "translate(" +
  139. _widthHalf +
  140. "px," +
  141. _heightHalf +
  142. "px)" +
  143. cameraCSSMatrix +
  144. matrix3d
  145. );
  146. }
  147. return "translate(-50%,-50%)" + matrix3d;
  148. }
  149. function renderObject(object, camera, cameraCSSMatrix) {
  150. if (object instanceof CSS3DObject) {
  151. var style;
  152. if (object instanceof CSS3DSprite) {
  153. // http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
  154. matrix.copy(camera.matrixWorldInverse);
  155. matrix.transpose();
  156. matrix.copyPosition(object.matrixWorld);
  157. matrix.scale(object.scale);
  158. matrix.elements[3] = 0;
  159. matrix.elements[7] = 0;
  160. matrix.elements[11] = 0;
  161. matrix.elements[15] = 1;
  162. style = getObjectCSSMatrix(matrix, cameraCSSMatrix);
  163. } else {
  164. style = getObjectCSSMatrix(object.matrixWorld, cameraCSSMatrix);
  165. }
  166. var element = object.element;
  167. var cachedObject = cache.objects.get(object);
  168. if (cachedObject === undefined || cachedObject.style !== style) {
  169. element.style.WebkitTransform = style;
  170. element.style.transform = style;
  171. var objectData = { style: style };
  172. if (isIE) {
  173. objectData.distanceToCameraSquared = getDistanceToSquared(
  174. camera,
  175. object
  176. );
  177. }
  178. cache.objects.set(object, objectData);
  179. }
  180. if (element.parentNode !== cameraElement) {
  181. cameraElement.appendChild(element);
  182. }
  183. }
  184. for (var i = 0, l = object.children.length; i < l; i++) {
  185. renderObject(object.children[i], camera, cameraCSSMatrix);
  186. }
  187. }
  188. var getDistanceToSquared = (function () {
  189. var a = new THREE.Vector3();
  190. var b = new THREE.Vector3();
  191. return function (object1, object2) {
  192. a.setFromMatrixPosition(object1.matrixWorld);
  193. b.setFromMatrixPosition(object2.matrixWorld);
  194. return a.distanceToSquared(b);
  195. };
  196. })();
  197. function filterAndFlatten(scene) {
  198. var result = [];
  199. scene.traverse(function (object) {
  200. if (object instanceof THREE.CSS3DObject) result.push(object);
  201. });
  202. return result;
  203. }
  204. function zOrder(scene) {
  205. var sorted = filterAndFlatten(scene).sort(function (a, b) {
  206. var distanceA = cache.objects.get(a).distanceToCameraSquared;
  207. var distanceB = cache.objects.get(b).distanceToCameraSquared;
  208. return distanceA - distanceB;
  209. });
  210. var zMax = sorted.length;
  211. for (var i = 0, l = sorted.length; i < l; i++) {
  212. sorted[i].element.style.zIndex = zMax - i;
  213. }
  214. }
  215. this.render = function (scene, camera) {
  216. var fov = camera.projectionMatrix.elements[5] * _heightHalf;
  217. if (cache.camera.fov !== fov) {
  218. if (camera.isPerspectiveCamera) {
  219. domElement.style.WebkitPerspective = fov + "px";
  220. domElement.style.perspective = fov + "px";
  221. }
  222. cache.camera.fov = fov;
  223. }
  224. scene.updateMatrixWorld();
  225. if (camera.parent === null) camera.updateMatrixWorld();
  226. if (camera.isOrthographicCamera) {
  227. var tx = -(camera.right + camera.left) / 2;
  228. var ty = (camera.top + camera.bottom) / 2;
  229. }
  230. var cameraCSSMatrix = camera.isOrthographicCamera
  231. ? "scale(" +
  232. fov +
  233. ")" +
  234. "translate(" +
  235. epsilon(tx) +
  236. "px," +
  237. epsilon(ty) +
  238. "px)" +
  239. getCameraCSSMatrix(camera.matrixWorldInverse)
  240. : "translateZ(" +
  241. fov +
  242. "px)" +
  243. getCameraCSSMatrix(camera.matrixWorldInverse);
  244. var style =
  245. cameraCSSMatrix + "translate(" + _widthHalf + "px," + _heightHalf + "px)";
  246. if (cache.camera.style !== style && !isIE) {
  247. cameraElement.style.WebkitTransform = style;
  248. cameraElement.style.transform = style;
  249. cache.camera.style = style;
  250. }
  251. renderObject(scene, camera, cameraCSSMatrix);
  252. if (isIE) {
  253. // IE10 and 11 does not support 'preserve-3d'.
  254. // Thus, z-order in 3D will not work.
  255. // We have to calc z-order manually and set CSS z-index for IE.
  256. // FYI: z-index can't handle object intersection
  257. zOrder(scene);
  258. }
  259. };
  260. }