Без опису
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. /*
  2. * 3d.js
  3. *
  4. * Copyright (c) 2024 Thomas Buck (thomas@xythobuz.de)
  5. */
  6. import * as THREE from 'three';
  7. import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
  8. import { STLLoader } from 'three/addons/loaders/STLLoader.js'
  9. import { VRMLLoader } from 'three/addons/loaders/VRMLLoader.js';
  10. // https://wejn.org/2020/12/cracking-the-threejs-object-fitting-nut/
  11. function fitCameraToObject(camera, object, offset, orbitControls, yOffset) {
  12. const boundingBox = new THREE.Box3();
  13. boundingBox.setFromObject( object );
  14. var middle = new THREE.Vector3();
  15. boundingBox.getCenter(middle);
  16. var size = new THREE.Vector3();
  17. boundingBox.getSize(size);
  18. // figure out how to fit the box in the view:
  19. // 1. figure out horizontal FOV (on non-1.0 aspects)
  20. // 2. figure out distance from the object in X and Y planes
  21. // 3. select the max distance (to fit both sides in)
  22. //
  23. // The reason is as follows:
  24. //
  25. // Imagine a bounding box (BB) is centered at (0,0,0).
  26. // Camera has vertical FOV (camera.fov) and horizontal FOV
  27. // (camera.fov scaled by aspect, see fovh below)
  28. //
  29. // Therefore if you want to put the entire object into the field of view,
  30. // you have to compute the distance as: z/2 (half of Z size of the BB
  31. // protruding towards us) plus for both X and Y size of BB you have to
  32. // figure out the distance created by the appropriate FOV.
  33. //
  34. // The FOV is always a triangle:
  35. //
  36. // (size/2)
  37. // +--------+
  38. // | /
  39. // | /
  40. // | /
  41. // | F° /
  42. // | /
  43. // | /
  44. // | /
  45. // |/
  46. //
  47. // F° is half of respective FOV, so to compute the distance (the length
  48. // of the straight line) one has to: `size/2 / Math.tan(F)`.
  49. //
  50. // FTR, from https://threejs.org/docs/#api/en/cameras/PerspectiveCamera
  51. // the camera.fov is the vertical FOV.
  52. const fov = camera.fov * ( Math.PI / 180 );
  53. const fovh = 2*Math.atan(Math.tan(fov/2) * camera.aspect);
  54. let dx = size.z / 2 + Math.abs( size.x / 2 / Math.tan( fovh / 2 ) );
  55. let dy = size.z / 2 + Math.abs( size.y / 2 / Math.tan( fov / 2 ) );
  56. let cameraZ = Math.max(dx, dy);
  57. // offset the camera, if desired (to avoid filling the whole canvas)
  58. if( offset !== undefined && offset !== 0 ) cameraZ *= offset;
  59. camera.target = middle;
  60. camera.position.set( middle.x, middle.y + yOffset * cameraZ, middle.z + cameraZ );
  61. // set the far plane of the camera so that it easily encompasses the whole object
  62. const minZ = boundingBox.min.z;
  63. const cameraToFarEdge = ( minZ < 0 ) ? -minZ + cameraZ : cameraZ - minZ;
  64. camera.far = cameraToFarEdge * 3;
  65. camera.updateProjectionMatrix();
  66. if ( orbitControls !== undefined ) {
  67. // set camera to rotate around the center
  68. orbitControls.target = middle;
  69. // prevent camera from zooming out far enough to create far plane cutoff
  70. orbitControls.maxDistance = cameraToFarEdge * 2;
  71. }
  72. }
  73. export function init_3d(path, container, status, div_width, div_height) {
  74. const width = div_width;
  75. const height = div_height;
  76. const scene = new THREE.Scene();
  77. //scene.add(new THREE.AxesHelper(1));
  78. const camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
  79. const renderer = new THREE.WebGLRenderer();
  80. renderer.setSize(width, height);
  81. const controls = new OrbitControls(camera, renderer.domElement);
  82. controls.enableDamping = true;
  83. controls.autoRotate = true;
  84. if (path.endsWith(".stl")) {
  85. const light_amb = new THREE.AmbientLight(0x424242);
  86. scene.add(light_amb);
  87. for (const i of [1, -1]) {
  88. for (const j of [0, 1, 2]) {
  89. const light = new THREE.DirectionalLight(0xffffff, 0.5);
  90. light.position.set(i * (j == 0 ? 1 : 0),
  91. i * (j == 1 ? 1 : 0),
  92. i * (j == 2 ? 1 : 0));
  93. scene.add(light);
  94. }
  95. }
  96. const material = new THREE.MeshStandardMaterial();
  97. //material.roughness = 0.75;
  98. const loader = new STLLoader();
  99. loader.load(
  100. path,
  101. function (geometry) {
  102. const mesh = new THREE.Mesh(geometry, material);
  103. mesh.rotation.setFromVector3(new THREE.Vector3(-Math.PI / 2, 0, 0));
  104. scene.add(mesh);
  105. fitCameraToObject(camera, scene, 0, controls, 0);
  106. controls.update();
  107. },
  108. (xhr) => {
  109. const s = (xhr.loaded / xhr.total) * 100 + '% loaded';
  110. console.log(s);
  111. status.textContent = s;
  112. },
  113. (error) => {
  114. console.log(error);
  115. status.textContent = error;
  116. }
  117. );
  118. } else if (path.endsWith(".wrl")) {
  119. const light_amb = new THREE.AmbientLight(0xffffff);
  120. scene.add(light_amb);
  121. const loader = new VRMLLoader();
  122. loader.load(
  123. path,
  124. function (object) {
  125. scene.add(object);
  126. fitCameraToObject(camera, scene, 0, controls, 0);
  127. controls.update();
  128. },
  129. (xhr) => {
  130. const s = (xhr.loaded / xhr.total) * 100 + '% loaded';
  131. console.log(s);
  132. status.textContent = s;
  133. },
  134. (error) => {
  135. console.log(error);
  136. status.textContent = error;
  137. }
  138. );
  139. } else {
  140. const s = "error: unknown filetype for " + path;
  141. console.log(s);
  142. status.textContent = s;
  143. }
  144. camera.position.z = 50;
  145. controls.update();
  146. function animate() {
  147. requestAnimationFrame(animate);
  148. controls.update();
  149. renderer.render(scene, camera);
  150. }
  151. animate();
  152. status.textContent = "3D model ready!";
  153. container.appendChild(renderer.domElement);
  154. const div = document.createElement("div");
  155. div.style.position = "absolute";
  156. div.style.left = "5px";
  157. div.style.top = "5px";
  158. div.style.background = "white";
  159. div.style.color = "black";
  160. container.appendChild(div);
  161. const chk_ar = document.createElement("input");
  162. chk_ar.type = "checkbox";
  163. chk_ar.checked = true;
  164. chk_ar.addEventListener('change', function() {
  165. controls.autoRotate = this.checked;
  166. });
  167. const div_ar = document.createElement("div");
  168. div_ar.appendChild(chk_ar);
  169. div_ar.appendChild(document.createTextNode("Auto-Rotate"));
  170. div.appendChild(div_ar);
  171. const btn_rst = document.createElement("input");
  172. btn_rst.type = "button";
  173. btn_rst.value = "Reset Camera";
  174. btn_rst.addEventListener('click', function() {
  175. fitCameraToObject(camera, scene, 0, controls, 0);
  176. controls.update();
  177. });
  178. const div_rst = document.createElement("div");
  179. div_rst.appendChild(btn_rst);
  180. div.appendChild(div_rst);
  181. }