scene.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. var scene;
  2. var look_at;
  3. var camera;
  4. var camera_tween;
  5. var orbitControls;
  6. var texture_loader = new THREE.TextureLoader();
  7. var stl_loader = new THREE.STLLoader();
  8. var objects = new Map();
  9. const None = null;
  10. const False = false;
  11. const True = true;
  12. function texture_geometry(coords) {
  13. const geometry = new THREE.BufferGeometry();
  14. const nI = coords[0].length;
  15. const nJ = coords.length;
  16. const vertices = [];
  17. const indices = [];
  18. const uvs = [];
  19. for (let j = 0; j < nJ; ++j) {
  20. for (let i = 0; i < nI; ++i) {
  21. const XYZ = coords[j][i] || [0, 0, 0];
  22. vertices.push(...XYZ);
  23. uvs.push(i / (nI - 1), j / (nJ - 1));
  24. }
  25. }
  26. for (let j = 0; j < nJ - 1; ++j) {
  27. for (let i = 0; i < nI - 1; ++i) {
  28. if (coords[j][i] && coords[j][i + 1] && coords[j + 1][i] && coords[j + 1][i + 1]) {
  29. const idx00 = i + j * nI;
  30. const idx10 = i + j * nI + 1;
  31. const idx01 = i + j * nI + nI;
  32. const idx11 = i + j * nI + 1 + nI;
  33. indices.push(idx10, idx00, idx01);
  34. indices.push(idx11, idx10, idx01);
  35. }
  36. }
  37. }
  38. geometry.setIndex(new THREE.Uint32BufferAttribute(indices, 1));
  39. geometry.setAttribute("position", new THREE.Float32BufferAttribute(vertices, 3));
  40. geometry.setAttribute("uv", new THREE.Float32BufferAttribute(uvs, 2));
  41. geometry.computeVertexNormals();
  42. geometry.computeFaceNormals();
  43. return geometry;
  44. }
  45. function texture_material(texture) {
  46. texture.flipY = false;
  47. texture.minFilter = THREE.LinearFilter;
  48. return new THREE.MeshLambertMaterial({
  49. map: texture,
  50. side: THREE.DoubleSide,
  51. });
  52. }
  53. export default {
  54. template: `
  55. <div style="position:relative">
  56. <canvas style="position:relative"></canvas>
  57. <div style="position:absolute;pointer-events:none;top:0"></div>
  58. <div style="position:absolute;pointer-events:none;top:0"></div>
  59. </div>`,
  60. mounted() {
  61. scene = new THREE.Scene();
  62. objects.set("scene", scene);
  63. look_at = new THREE.Vector3(0, 0, 0);
  64. camera = new THREE.PerspectiveCamera(75, this.width / this.height, 0.1, 1000);
  65. camera.lookAt(look_at);
  66. camera.up = new THREE.Vector3(0, 0, 1);
  67. camera.position.set(0, -3, 5);
  68. scene.add(new THREE.AmbientLight(0xffffff, 0.7));
  69. const light = new THREE.DirectionalLight(0xffffff, 0.3);
  70. light.position.set(5, 10, 40);
  71. scene.add(light);
  72. const renderer = new THREE.WebGLRenderer({
  73. antialias: true,
  74. alpha: true,
  75. canvas: this.$el.children[0],
  76. });
  77. renderer.setClearColor("#eee");
  78. renderer.setSize(this.width, this.height);
  79. const text_renderer = new THREE.CSS2DRenderer({
  80. element: this.$el.children[1],
  81. });
  82. text_renderer.setSize(this.width, this.height);
  83. const text3d_renderer = new THREE.CSS3DRenderer({
  84. element: this.$el.children[2],
  85. });
  86. text3d_renderer.setSize(this.width, this.height);
  87. const ground = new THREE.Mesh(new THREE.PlaneGeometry(100, 100), new THREE.MeshPhongMaterial({ color: "#eee" }));
  88. ground.translateZ(-0.01);
  89. ground.object_id = "ground";
  90. scene.add(ground);
  91. const grid = new THREE.GridHelper(100, 100);
  92. grid.material.transparent = true;
  93. grid.material.opacity = 0.2;
  94. grid.rotateX(Math.PI / 2);
  95. scene.add(grid);
  96. orbitControls = new THREE.OrbitControls(camera, renderer.domElement);
  97. const render = function () {
  98. requestAnimationFrame(() => setTimeout(() => render(), 1000 / 20));
  99. TWEEN.update();
  100. renderer.render(scene, camera);
  101. text_renderer.render(scene, camera);
  102. text3d_renderer.render(scene, camera);
  103. };
  104. render();
  105. const raycaster = new THREE.Raycaster();
  106. const click_handler = (mouseEvent) => {
  107. let x = (mouseEvent.offsetX / renderer.domElement.width) * 2 - 1;
  108. let y = -(mouseEvent.offsetY / renderer.domElement.height) * 2 + 1;
  109. raycaster.setFromCamera({ x: x, y: y }, camera);
  110. this.$emit("click", {
  111. hits: raycaster
  112. .intersectObjects(scene.children, true)
  113. .filter((o) => o.object.object_id)
  114. .map((o) => ({
  115. object_id: o.object.object_id,
  116. point: o.point,
  117. })),
  118. click_type: mouseEvent.type,
  119. shift_key: mouseEvent.shiftKey,
  120. });
  121. };
  122. this.$el.onclick = click_handler;
  123. this.$el.ondblclick = click_handler;
  124. },
  125. methods: {
  126. create(type, id, parent_id, ...args) {
  127. let mesh;
  128. if (type == "group") {
  129. mesh = new THREE.Group();
  130. } else if (type == "line") {
  131. const start = new THREE.Vector3(...args[0]);
  132. const end = new THREE.Vector3(...args[1]);
  133. const geometry = new THREE.BufferGeometry().setFromPoints([start, end]);
  134. const material = new THREE.LineBasicMaterial({ transparent: true });
  135. mesh = new THREE.Line(geometry, material);
  136. } else if (type == "curve") {
  137. const curve = new THREE.CubicBezierCurve3(
  138. new THREE.Vector3(...args[0]),
  139. new THREE.Vector3(...args[1]),
  140. new THREE.Vector3(...args[2]),
  141. new THREE.Vector3(...args[3])
  142. );
  143. const points = curve.getPoints(args[4] - 1);
  144. const geometry = new THREE.BufferGeometry().setFromPoints(points);
  145. const material = new THREE.LineBasicMaterial({ transparent: true });
  146. mesh = new THREE.Line(geometry, material);
  147. } else if (type == "text") {
  148. const div = document.createElement("div");
  149. div.textContent = args[0];
  150. div.style.cssText = args[1];
  151. mesh = new THREE.CSS2DObject(div);
  152. } else if (type == "text3d") {
  153. const div = document.createElement("div");
  154. div.textContent = args[0];
  155. div.style.cssText = "userSelect:none;" + args[1];
  156. mesh = new THREE.CSS3DObject(div);
  157. } else if (type == "texture") {
  158. const url = args[0];
  159. const coords = args[1];
  160. const geometry = texture_geometry(coords);
  161. const material = texture_material(texture_loader.load(url));
  162. mesh = new THREE.Mesh(geometry, material);
  163. } else if (type == "spot_light") {
  164. mesh = new THREE.Group();
  165. const light = new THREE.SpotLight(...args);
  166. light.position.set(0, 0, 0);
  167. light.target = new THREE.Object3D();
  168. light.target.position.set(1, 0, 0);
  169. mesh.add(light);
  170. mesh.add(light.target);
  171. } else {
  172. let geometry;
  173. const wireframe = args.pop();
  174. if (type == "box") geometry = new THREE.BoxGeometry(...args);
  175. if (type == "sphere") geometry = new THREE.SphereGeometry(...args);
  176. if (type == "cylinder") geometry = new THREE.CylinderGeometry(...args);
  177. if (type == "ring") geometry = new THREE.RingGeometry(...args);
  178. if (type == "quadratic_bezier_tube") {
  179. const curve = new THREE.QuadraticBezierCurve3(
  180. new THREE.Vector3(...args[0]),
  181. new THREE.Vector3(...args[1]),
  182. new THREE.Vector3(...args[2])
  183. );
  184. geometry = new THREE.TubeGeometry(curve, ...args.slice(3));
  185. }
  186. if (type == "extrusion") {
  187. const shape = new THREE.Shape();
  188. const outline = args[0];
  189. const height = args[1];
  190. shape.autoClose = true;
  191. shape.moveTo(outline[0][0], outline[0][1]);
  192. outline.slice(1).forEach((p) => shape.lineTo(p[0], p[1]));
  193. const settings = { depth: height, bevelEnabled: false };
  194. geometry = new THREE.ExtrudeGeometry(shape, settings);
  195. }
  196. if (type == "stl") {
  197. const url = args[0];
  198. geometry = new THREE.BufferGeometry();
  199. stl_loader.load(url, (geometry) => (mesh.geometry = geometry));
  200. }
  201. let material;
  202. if (wireframe) {
  203. mesh = new THREE.LineSegments(
  204. new THREE.EdgesGeometry(geometry),
  205. new THREE.LineBasicMaterial({ transparent: true })
  206. );
  207. } else {
  208. material = new THREE.MeshPhongMaterial({ transparent: true });
  209. mesh = new THREE.Mesh(geometry, material);
  210. }
  211. }
  212. mesh.object_id = id;
  213. objects.set(id, mesh);
  214. objects.get(parent_id).add(objects.get(id));
  215. },
  216. material(object_id, color, opacity, side) {
  217. const material = objects.get(object_id).material;
  218. if (!material) return;
  219. material.color.set(color);
  220. material.opacity = opacity;
  221. if (side == "front") material.side = THREE.FrontSide;
  222. else if (side == "back") material.side = THREE.BackSide;
  223. else material.side = THREE.DoubleSide;
  224. },
  225. move(object_id, x, y, z) {
  226. objects.get(object_id).position.set(x, y, z);
  227. },
  228. scale(object_id, sx, sy, sz) {
  229. objects.get(object_id).scale.set(sx, sy, sz);
  230. },
  231. rotate(object_id, R) {
  232. const R4 = new THREE.Matrix4().makeBasis(
  233. new THREE.Vector3(...R[0]),
  234. new THREE.Vector3(...R[1]),
  235. new THREE.Vector3(...R[2])
  236. );
  237. objects.get(object_id).rotation.setFromRotationMatrix(R4.transpose());
  238. },
  239. visible(object_id, value) {
  240. objects.get(object_id).visible = value;
  241. },
  242. delete(object_id) {
  243. objects.get(object_id).removeFromParent();
  244. objects.delete(object_id);
  245. },
  246. set_texture_url(object_id, url) {
  247. const obj = objects.get(object_id);
  248. if (obj.busy) return;
  249. obj.busy = true;
  250. const on_success = (texture) => {
  251. obj.material = texture_material(texture);
  252. obj.busy = false;
  253. };
  254. const on_error = () => (obj.busy = false);
  255. texture_loader.load(url, on_success, undefined, on_error);
  256. },
  257. set_texture_coordinates(object_id, coords) {
  258. objects.get(object_id).geometry = texture_geometry(coords);
  259. },
  260. move_camera(x, y, z, look_at_x, look_at_y, look_at_z, up_x, up_y, up_z, duration) {
  261. if (camera_tween) camera_tween.stop();
  262. camera_tween = new TWEEN.Tween([
  263. camera.position.x,
  264. camera.position.y,
  265. camera.position.z,
  266. camera.up.x,
  267. camera.up.y,
  268. camera.up.z,
  269. look_at.x,
  270. look_at.y,
  271. look_at.z,
  272. ])
  273. .to(
  274. [
  275. x === null ? camera.position.x : x,
  276. y === null ? camera.position.y : y,
  277. z === null ? camera.position.z : z,
  278. up_x === null ? camera.up.x : up_x,
  279. up_y === null ? camera.up.y : up_y,
  280. up_z === null ? camera.up.z : up_z,
  281. look_at_x === null ? look_at.x : look_at_x,
  282. look_at_y === null ? look_at.y : look_at_y,
  283. look_at_z === null ? look_at.z : look_at_z,
  284. ],
  285. duration * 1000
  286. )
  287. .onUpdate((p) => {
  288. camera.position.set(p[0], p[1], p[2]);
  289. camera.up.set(p[3], p[4], p[5]); // NOTE: before calling lookAt
  290. look_at.set(p[6], p[7], p[8]);
  291. camera.lookAt(p[6], p[7], p[8]);
  292. })
  293. .start();
  294. },
  295. },
  296. props: {
  297. width: Number,
  298. height: Number,
  299. },
  300. };