In recent days, I have been learning three.js, because I believe that only practice can produce truth, so I have made a simple panorama. Here I mainly share the problems encountered in making this VUE version of panorama. Some codes may be similar to those of other masters who have made panorama after all, the reasons are similar 😀

This article belongs to the technical summary class article

It will introduce how to install and use Three. js and some supporting plug-ins in Vue, the principle of using Three. js to realize panorama, the problem of image display after vue packaging, and the problem of not using Three. js in 32-bit Google 49 browser, etc. How to install the Node service will not be discussed here

  • Install three.js and the accompanying plug-ins in vue

  1. NPM install three. Jsnpm install threeThen import all the functional modules of Three into the corresponding page

    Three.js-npm address
<script> import * as THREE from "three"; . </script>Copy the code
  1. NPM installs OrbitControls.js to manipulate 3d scenesnpm install three-orbit-controlsWhen introducing the plug-in, you must ensure that three is successfully introduced, otherwise the page will report an error. If you do not want to download through NPM, in fact, you have downloaded the corresponding plug-in in NPM Threenode_modulesFind it in the folderthree/examples/jsm/controls/OrbitControlsThe plugin can also be found in this path, and can be imported in the form described in the comments below, but this is not recommended because it is not available in Google 32-bit 49 browsers
<script>
import * as THREE from "three";
const OrbitControls = require('three-orbit-controls')(THREE);
//import {OrbitControls} from 'three/examples/jsm/controls/OrbitControls'
...
</script>
Copy the code
  1. NPM I –save three-obj-mtr-loader to load.obj model files, MTL material information file here I don’t want to repeat too much, you can have a look at section 14 of Teacher Guo Longbon’s ebook guide, and FBX model file, that is, in addition to containing geometry, material information, but also can store data such as bone animation model file. It can be installed via NPM I three-FbX-loader

    When it comes to the import of these material files, I can’t help but make fun of them. STL format, most OBJ files are done in accordance with the official import method, there is no problem, but. FBX format is very special, I downloaded a lot of FBX format models and corresponding materials on the Internet before, but most of them can not use, I really cracked.

    The reason is that the compatibility of the plugin is not good. Most of the FBX animations downloaded online are not usable. There are some problems with the version and the file itself. There are some problems with the version and the file itself. There are some problems with the version and the file itself

<script> import * as THREE from "three"; import {OBJLoader,MTLLoader} from 'three-obj-mtl-loader'; const OrbitControls = require('three-orbit-controls')(THREE); . </script>Copy the code
  1. NPM installs the performance check plug-in,npm i three-statsThe main function is mainly used to detect the number of frames when the animation is running

<script>
import * as THREE from "three";
import {OBJLoader,MTLLoader} from 'three-obj-mtl-loader';
const OrbitControls = require('three-orbit-controls')(THREE);
import * as ThreeStats from 'three-stats'
...
</script>
Copy the code

The configuration file of the current project has been introduced here. Later, I will introduce some basic principles of three.js and the implementation of panorama. I will post the complete code at the bottom of the article

  • Fundamentals of three.js (renderer-renderer, scene-scene, camera-camera)

Here is a simple explanation of the principle. If you want to know more about it, please enter guo Longbang’s e-book guide

For example, if I am a director, I have prepared the best actor, but also asked the island first-class shooting team, finally look for a geomantic treasure ground, ready to shoot a let a person’s blood boiling youth idol action, one war famous, and then towards the peak of life 😋

  1. Renderer is just like that piece of fengfeng treasure, I have everything ready always need to find a suitable place to shoot, here is the renderer to create a custom size location renderer
new THREE.WebGLRenderer(); // Create rendererCopy the code
<template> <div ref='threeDom'></div> </template> <script> rendererInit(){var width = 1000; InnerWidth The width of the viewable area of the browser window (excluding the browser console, menu bar, and toolbar). // Window height window.innerheight this.renderer = new three.webglrenderer (); / / create the renderer enclosing the renderer. SetClearColor (0 XFFFFFF); // Add background color this.renderer.setSize(width, height); / / set the renderer size enclosing $refs. ThreeDom. The appendChild (enclosing the renderer. DomElement accordingly). }, </script>Copy the code
  1. Scene-scene is when you have a location and there’s nothing in it. It’s dark and you can’t see anything. Should we, as directors, put lights on and invite the actors in(In this case, the light source represents – ambient light, and the actor represents – created models)Otherwise, we are not qualified as a director, then how to move towards the peak of life ah
SceneInit (){// Initialize the scene and add light and auxiliary coordinates to the scene this.scene = new three.scene (); Var ambient = new THREE.AmbientLight(0x444444, 3); Var axisHelper = new three. AxesHelper(600); This.scene. add(ambient, axisHelper); // Modelling (){this.mygroup = new three.group (); Var textureLoader = new three.textureLoader (); Var img = textureLoader.load(require('.. /.. /public/img/qjt.jpeg')); var geometry = new THREE.SphereGeometry(130, 256, 256); / var/ball grid model material = new THREE. MeshLambertMaterial ({map: img, / / set the color map attribute value side: THREE. DoubleSide, / / double-sided rendering}); var meshSphere = new THREE.Mesh(geometry, material); // Mesh model object Mesh. Name = 'sphere container '; this.mygroup.add(meshSphere); this.scene.add(this.mygroup); . }Copy the code
  1. Camera-camera, as its name implies, is a prop used for shooting, and its perspective is also the perspective of our final picture. Here, we use perspective camera because the perspective of perspective camera is closer to the perspective of real people’s eyes. The specific parameters of perspective camera can be seen in the Chinese document of perspective camera
CameraInit () {// Initialize the camera var width = 800; Var height = 800; // PerspectiveCamera = new THREE.PerspectiveCamera(90, width/height, 1, 1000); // Use perspective camera this.camera.position.set(0, 0, 10); // Set the camera position this.camera. LookAt (new three.vector3 (0, 0, 0)); // the camera looks at},Copy the code
  • Start implementing simple panoramas

Finally, here we are, and now we are officially working on 😤. First, we can work out the renderer, scene-scene and camera-camera based on the basic principles introduced above, and then we can realize the panorama by creating a The ball with picture texture is not necessarily a sphere at this point, other shapes can also be implemented, depending on the usage scenario

  1. Start by creating a sphere mesh model and corresponding texture maps

    • The default rendering mode for Chinese document texture maps is THREE.FrontSide is used to render in front of each other
Model.mygroup = new three.group (); // modelling(){this.mygroup = new three.group (); var textureLoader = new THREE.TextureLoader(); Var img = textureLoader.load(require('.. /.. /public/img/home3.jpeg')); var geometry = new THREE.SphereGeometry(130, 256, 256); / var/ball grid model material = new THREE. MeshLambertMaterial ({map: img, / / set the color map attribute value side: THREE. DoubleSide, / / double-sided rendering}); var meshSphere = new THREE.Mesh(geometry, material); // Mesh model object Mesh. Name = 'sphere container '; this.mygroup.add(meshSphere); this.scene.add(this.mygroup); }},Copy the code

  1. Create rectangular plane custom text

There are several ways to customize text in three.js

The way words are formed Implementation scheme advantages disadvantages
DOM + CSS A common implementation uses absolute positioning and a z-index large enough to place components or text above the 3D graphics Simple implementation is powerful Poor 3d effect and object linkage
THREE.CanvasTexture Draw text in a canvas, then map it using CanvasTexture as a texture The text effect is relatively rich Once generated, the resolution is fixed and distortion occurs when amplified
THREE.TextGeometry Use native TextGeometry for rendering generation Good effect, can be synchronized with the scene Font color and animation are complex and resource – consuming
3D font model Use 3d font models and threejs for load control Good effect, can be customized effect Loading the model costs resources and font content cannot be customized
Bitmap fonts The text template is generated by BmpFont, and then loaded and displayed Customize font and effects Loading the model costs resources and font content cannot be customized
Three.Sprite material Sprite loads image textures Always facing camera plane, suitable for label display Once generated, the resolution is fixed and distortion occurs when amplified

Here, I choose canvas text. Why is it convenient to customize text without importing pictures

Model.mygroup = new three.group (); // modelling(){this.mygroup = new three.group (); Var canvasText = this.getcanvers(' enter '); Var texture = new THREE.CanvasTexture(canvasText); var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); Var materialText = new THREE.MeshPhongMaterial({map: texture, side: THREE.DoubleSide, // Double render}); var meshText = new THREE.Mesh(geometryText, materialText); Meshtext. name = 'enter '; meshText.position.set(40, 20, -90) this.mygroup.add(meshText); this.scene.add(this.mygroup); }, getCanvers (text) {// Generate a canvers pattern var canvasText = document.createElement("canvas"); var c = canvasText.getContext('2d'); C. fillstyle = "#FFFFFF"; //canver background c.fillrect (0, 0, 300, 200); // Generate a rectangle c.ranslate (160, 80); c.fillStyle = "#000000"; // text fill color c.font = "bold 100px 宋体"; C.extbaseline = "middle"; // Text and c.textalign = "center"; // Center the text c. filltext (text, 0, 0); var texture = new THREE.CanvasTexture(canvasText); //Canvas texture var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); Var materialText = new THREE.MeshPhongMaterial({map: texture, // Set side: THREE.DoubleSide, // Double render); var meshText = new THREE.Mesh(geometryText, materialText); meshText.name = text; meshText.position.set(40, 20, -90); return canvasText; }},Copy the code
  1. Switch the scene by clicking on the rectangular plane

    To trigger the click event in normal HTML, you only need to bind the event to the corresponding DOM. However, in three.js, it does not work, because the graphic page generated by three is actually a canvas and the corresponding DOM cannot be directly retrieved. Not to mention binding events to the DOM, but luckily three.js provides onenew THREE.Raycaster() Light projection (Used to pick up the position of the mouse and calculate what object the mouse has moved over in 3d space) The ray will record the intersecting geometry and return the mesh of the corresponding model from near to far in the form of array. All we need to do is pass the position of mouse and the current camera into the ray, so that we can get the current model clicked according to the name of the model and trigger the corresponding event

init(){ this.$refs.threeDom.addEventListener('dblclick', this.onMouseDblclick); }, onMouseDblclick(event){// Trigger the double click event // get the array where RayCaster and all the models meet, the elements are sorted by distance, Var intersects = this.getIntersects(event); var intersects = this.getIntersects(event); . }, getIntersects(event) {event.preventDefault(); Var rayCaster = new three.raycaster (); Var mouse = new three.vector2 (); var container = this.$refs.threeDom; let getBoundingClientRect = container.getBoundingClientRect(); / / through the mouse to click position, calculate the raycaster points needed for the position of the component, in order to screen for the center, the range of 1 to 1 mouse. X = ((event. ClientX - getBoundingClientRect. Left) / container.offsetWidth) * 2 - 1; mouse.y = -((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1; Raycaster. SetFromCamera (mouse, this.camera); / / fellowship with ray array of objects, including elements sorted by distance, the closer the top var intersects the = raycaster. IntersectObjects (this. Scene. Children [2]. The children); // Return intersects; },Copy the code
  1. Define the camera position

    We need to put the perspective projection camera in the center of the sphere to simulate the position of the person in the room, adjust the camera position and the camera to look at

CameraInit () {// Initialize the camera var width = 800; Var height = 800; // PerspectiveCamera = new THREE.PerspectiveCamera(90, width/height, 1, 1000); // Use perspective camera this.camera.position.set(0, 0, 10); // Set the camera position this.camera. LookAt (new three.vector3 (0, 0, 0)); // the camera looks at},Copy the code
  1. Initializing the Controller

    The controller is orbitControls.js which we introduced at the beginning to operate the 3d scene plug-in. The refresh mechanism of OrbitControls is to dynamically change the page by constantly re-rendering at a high frequency when the controller listens to the page changes

ControlInit (){// Initialize controller this.controls = New OrbitControls(this.camera, this.$refs.threedom); // Initialize controller this.controls.target.set(0, 0, 0); / / set the focus of the controller, the controller revolve around the focal point for this. Controls. The minDistance = 10; / / set the mobile the most short distance (the default is zero). This controls. MaxPolarAngle = math.h PI; / / orbit around the vertical distance (the range is 0 to Math. PI, the default for Math, PI) enclosing controls. MaxDistance = 30; // Set the maximum distance to move (default is infinite) this.controls.enablePan = false; / / disable right click function enclosing controls. AddEventListener (' change 'this. Refresh); }, refresh(){this.renderer.render(this.scene, this.camera); // Perform render operation this.stats.update(); // Update the performance monitoring value},Copy the code
  1. Defines a controllable automatic rotation animation

    After the above steps, the panorama function is almost complete, but the page does not automatically rotate, it always feels a little bit less interesting, now we add the function of automatic rotation to the project and can stop and turn on automatic rotation according to the button. KeyframeTrack() is used to define keyframes. New Three.animationClip () is used to clip the keyFrame object. New THREE.AnimationMixer() example of animation mixing

    If you want to understand the basic principles of animation in detail, you can read the article three.js-keyframetrack by Big Guy

    AddAnimation (){// Add and start animation this.clock = new three.clock (); Var times = [0, 3600]; Var position_x = [0, 360]; Var keyFrame = new THREE.KeyframeTrack('meshSphere. Rotation [y]', times, position_x); var duration = 100; Var cilp = new THREE.AnimationClip('sphereRotate', duration, [keyframe]); // Edit the keyFrame object this.mixer = new three.animationMixer (this.mygroup); Action = this.mixer.clipAction(cilp); this.action.timeScale = 1; This.action.setloop (three.looppingpong).play(); This.animate (); // Animate (); RotateAnimate = requestAnimationFrame(this.animate); this.renderer.render(this.scene, this.camera); this.update(); },Copy the code
  2. Panorama complete code

Expand to see the full code

<template>
	<div class="homePage">
		<el-card class="card">
			<div slot="header">
				<div class="card-title">
					<span>简易版全景图</span>

					<div class="card-property">
						<span ref='property'></span>
					</div>

				</div>
			</div>

			<div class="card-content">
				<div ref='threeDom' class="model"></div>
				<div class="control">
					<span class="control-title">控制台</span>
					<div class="control-block">
						<span class="control-block-title">是否自动旋转</span>
						<el-radio-group v-model="isRotate" @change="isSpin">
							<el-radio :label="1">开启</el-radio>
							<el-radio :label="0">关闭</el-radio>
						</el-radio-group>
					</div>
				</div>
			</div>
		</el-card>
	</div>
</template>

<script>
	import axios from 'axios';
	import * as THREE from "three";
	import * as TrackballControls from 'three-trackballcontrols'
	import * as ThreeStats from 'three-stats'
	import { OBJLoader, MTLLoader } from 'three-obj-mtl-loader';
	const OrbitControls = require('three-orbit-controls')(THREE);

	export default {
		props: {
			msg: String
		},
		data() {
			return {
				renderer: '', //渲染器
				scene: '', //场景
				light: '', //光源
				camera: '', //相机
				controls: '', //控制器
				stats: '', //性能监控器
				mygroup: '', //模型组

				action: '', //控制动画的值
				clock: '', //时钟
				mixer: '', //混合实例
				rotateAnimate: '', //旋转动画
				isRotate: 1, //是否开启旋转

			}
		},

		mounted() {
			this.init(); //初始化
		},

		methods: {
			init() {
				this.$refs.threeDom.addEventListener('dblclick', this.onMouseDblclick); //监听双击事件
				this.rendererInit(); //创建渲染器
				this.sceneInit(); //创建场景    包含光源和辅助坐标系
				this.cameraInit(); //创建相机
				this.controlInit(); //初始化控制器
				this.propertyInit(); //性能监控
				this.modelling(); //建立模型
			},

			modelling(){ //开始建立模型
				this.mygroup = new THREE.Group();
				var textureLoader = new THREE.TextureLoader(); //创建纹理贴图		
				var img = textureLoader.load(require('../../public/img/home3.jpeg'));

				var geometry = new THREE.SphereGeometry(130, 256, 256); // 球体网格模型
				var material = new THREE.MeshLambertMaterial({
					map: img, //设置颜色贴图属性值
					side: THREE.DoubleSide, //双面渲染
				});
				var meshSphere = new THREE.Mesh(geometry, material); //网格模型对象Mesh	
				meshSphere.name = '球体容器';
				this.mygroup.add(meshSphere);

				var canvasText = this.getcanvers('进门'); //生成一个canvers 文字图案对象
				var texture = new THREE.CanvasTexture(canvasText);
				var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60);
				var materialText = new THREE.MeshPhongMaterial({
					map: texture, // 设置纹理贴图
					side: THREE.DoubleSide, //双面渲染
				});
				var meshText = new THREE.Mesh(geometryText, materialText);
				meshText.name = '进门';
				meshText.position.set(40, 20, -90)
				this.mygroup.add(meshText);

				this.scene.add(this.mygroup);
				this.addAnimation(); //添加并开启动画
				this.refresh();
			},

			isSpin(val) { //开启和关闭旋转
				if (val == 0) { //关闭控制台		
					this.action.paused = true;
				} else {
					this.action.paused = false;
				}
			},

			addAnimation() { //添加并开启动画
				this.clock = new THREE.Clock(); // three.js 时钟对象
				var times = [0, 3600]; //	创建帧动画序列
				var position_x = [0, 360]; //离散属性值
				var keyframe = new THREE.KeyframeTrack('meshSphere.rotation[y]', times, position_x);
				var duration = 100; //持续时间
				var cilp = new THREE.AnimationClip('sphereRotate', duration, [keyframe]); //剪辑 keyframe对象
				this.mixer = new THREE.AnimationMixer(this.mygroup); //动画混合实例
				this.action = this.mixer.clipAction(cilp);
				this.action.timeScale = 1; //播放速度
				this.action.setLoop(THREE.LoopPingPong).play(); //开始播放 像乒乓球一样在起始点与结束点之间来回循环
				this.animate(); //开启动画
			},

			animate() { //循环渲染
				this.rotateAnimate = requestAnimationFrame(this.animate);
				this.renderer.render(this.scene, this.camera);
				this.update();
			},

			update() { //数据更新
				this.stats.update();
				this.mixer.update(this.clock.getDelta());
			},

			rendererInit() { //初始化渲染器
				var width = 1000; //窗口宽度
				var height = 800; //窗口高度
				this.renderer = new THREE.WebGLRenderer(); //创建渲染器
				this.renderer.setClearColor(0xffffff); //添加背景颜色
				this.renderer.setSize(width, height); // 设定渲染器尺寸
				this.$refs.threeDom.appendChild(this.renderer.domElement);
			},

			sceneInit() { //初始化场景 并向场景添加光源和辅助坐标系
				this.scene = new THREE.Scene();
				var ambient = new THREE.AmbientLight(0x444444, 3); //添加光源  颜色和光照强度
				var axisHelper = new THREE.AxesHelper(600); //添加辅助坐标系
				this.scene.add(ambient, axisHelper);
			},

			cameraInit() { //初始化相机
				var width = 800; //窗口宽度
				var height = 800; //窗口高度
				this.camera = new THREE.PerspectiveCamera(90, width / height, 1, 1000); //使用透视相机
				this.camera.position.set(0, 0, 10); //设置相机位置
				this.camera.lookAt(new THREE.Vector3(0, 0, 0)); // 相机看向
			},

			controlInit() { //初始化控制器
				this.controls = new OrbitControls(this.camera, this.$refs.threeDom); // 初始化控制器
				this.controls.target.set(0, 0, 0); // 设置控制器的焦点,使控制器围绕这个焦点进行旋转
				this.controls.minDistance = 10; // 设置移动的最短距离(默认为零)
				this.controls.maxPolarAngle = Math.PI; //绕垂直轨道的距离(范围是0-Math.PI,默认为Math.PI)
			    this.controls.maxDistance = 30; // 设置移动的最长距离(默认为无穷)
				this.controls.enablePan = false; //禁用右键功能
				this.controls.addEventListener('change', this.refresh); //监听鼠标、键盘事件 让整个控件可以拖动
			},

			propertyInit() { //初始化性能监控
				this.stats = new ThreeStats.Stats(); // 创建一个性能监视器	
				this.stats.dom.style.position = 'absolute';
				this.stats.dom.style.top = '-4px';
				this.$refs.property.appendChild(this.stats.dom);
				this.stats.update();
			},

			getcanvers(text) { //生成一个canvers图案
				var canvasText = document.createElement("canvas");
				var c = canvasText.getContext('2d');
				// 矩形区域填充背景
				c.fillStyle = "#FFFFFF"; //canver背景
				c.fillRect(0, 0, 300, 200); //生成一个矩形
				c.translate(160, 80);
				c.fillStyle = "#000000"; //文本填充颜色
				c.font = "bold 100px 宋体"; //字体样式设置
				c.textBaseline = "middle"; //文本与
				c.textAlign = "center"; //文本居中
				c.fillText(text, 0, 0);

				var texture = new THREE.CanvasTexture(canvasText); //Canvas纹理
				var geometryText = new THREE.PlaneGeometry(16, 10, 60, 60); //生成一个矩形平面
				var materialText = new THREE.MeshPhongMaterial({
					map: texture, // 设置纹理贴图
					side: THREE.DoubleSide, //双面渲染
				});
				var meshText = new THREE.Mesh(geometryText, materialText);
				meshText.name = text;
				meshText.position.set(40, 20, -90);
				return canvasText;
			},

			refresh(){ //刷新页面 
				this.renderer.render(this.scene, this.camera); //执行渲染操作
				this.stats.update(); //更新性能监控的值			
			},

			onMouseDblclick(event) { //触发双击事件
				// 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
				var intersects = this.getIntersects(event);
				if (intersects.length != 0) {
					for (var item of intersects) {
						if (item.object.name != '') { //找到第一个不等于空的模型 就是自定义最近的模型
							this.action.paused = true; //停止旋转			
							this.$confirm('是否切换场景?', '提示', {
								confirmButtonText: '切换',
								cancelButtonText: '取消',
								type: 'warning'
							}).then(() => {
								this.action.paused = false; //开启旋转
								if (item.object.name == '进门') {
									this.changeScene('enter'); //改变页面场景
								} else if (item.object.name == '返回') {
									this.changeScene('backtrack'); //改变页面场景
								}
							}).catch(() => {
								this.action.paused = false; //开启旋转
							});
							break;
						}
					}
				} else { //这里是未选中状态
				}
			},

			changeScene(type) {
				var img = '';
				var names = '';
				var canvasText = '';
				var textureLoader = new THREE.TextureLoader(); //创建纹理贴图		
				if (type == 'enter') {
					img = textureLoader.load(require('../../public/img/home1.jpg')); //vue加载图表需要用 require形式
					canvasText = this.getcanvers('返回'); //生成一个canvers 文字图案对象	
					names = '返回';
				} else if (type == 'backtrack') { //返回房间
					img = textureLoader.load(require('../../public/img/home3.jpeg')); //vue加载图表需要用 require形式	
					canvasText = this.getcanvers('进门'); //生成一个canvers 文字图案对象	
					names = '进门';
				}

				for (var item of this.scene.children[2].children) {
					if (item.name == '球体容器') { //切换贴图 进入下一张贴图					
						var material = new THREE.MeshLambertMaterial({
							map: img, //设置颜色贴图属性值
							side: THREE.DoubleSide, //双面渲染
						});
						item.material = material;
					} else if (item.name == '进门' || item.name == '返回') {
						var texture = new THREE.CanvasTexture(canvasText);
						var materialText = new THREE.MeshPhongMaterial({
							map: texture, // 设置纹理贴图
							side: THREE.DoubleSide, //双面渲染
						});

						item.name = names; //改名模型的名字
						item.material = materialText;
					}
				}

				setTimeout(() => { //延迟刷新
					this.refresh();
				}, 100)

			},

			getIntersects(event) { // 获取与射线相交的对象数组
				event.preventDefault();
				// 声明 raycaster 和 mouse 变量
				var raycaster = new THREE.Raycaster(); //生成射线
				var mouse = new THREE.Vector2();
				var container = this.$refs.threeDom;
				let getBoundingClientRect = container.getBoundingClientRect();
				// 通过鼠标点击位置,计算出 raycaster 所需点的位置 分量,以屏幕为中心点,范围 -1 到 1
				mouse.x = ((event.clientX - getBoundingClientRect.left) / container.offsetWidth) * 2 - 1;
				mouse.y = -((event.clientY - getBoundingClientRect.top) / container.offsetHeight) * 2 + 1;
				//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
				raycaster.setFromCamera(mouse, this.camera);
				// 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
				var intersects = raycaster.intersectObjects(this.scene.children[2].children);
				//返回选中的对象
				return intersects;
			},
		}
	}
</script>

<style>
	.homePage {
		position: absolute;
		height: 100%;
		width: 100%;
		font-size: 14px;
		color: #303133;
		display: flex;
		align-items: center;
		justify-content: center;
	}

	.card {
		width: 1300px;
		height: 900px;
	}

	.card-title {
		display: flex;
		align-items: center;
		justify-content: space-between;
	}

	.card-title span {
		font-weight: 600;
		font-size: 18px;
	}

	.card-property {
		position: relative;
		width: 70px;
		height: 40px;
	}

	.card-content {
		display: flex;
		flex-direction: row;
	}

	.model {
		border: 1px solid #DCDFE6;
	}

	.control {
		display: flex;
		flex-direction: column;
		width: 300px;
		height: 800px;
		border: 1px solid #DCDFE6;
		border-left: none;
	}

	.control-title {
		font-size: 18px;
		font-weight: 600;
		text-align: center;
		color: #409EFF;
		padding: 10px;
		border-bottom: 1px solid #DCDFE6;
	}

	.control-block {
		padding: 10px;
		border-bottom: 1px solid #DCDFE6;
	}

	.control-block-title {
		display: block;
		margin-bottom: 5px;
	}

	/* 自定义element样式 */
	.el-card__header {
		padding: 10px 20px;
	}
</style>

Copy the code

Copy the code

Vue + three.js panorama GitHub address

  • Problems encountered in building this panorama

    1. Failed to insert image directly in VUE

      Since we are using a node.js enabled front-end service, we need to use require(‘.. /.. /public/img/home3.jpeg’), using the address image will not be displayed

    2. Images are not displayed after vue is packed

    Node changes the file protocol to the file:// format for accessing locally packaged images, whereas textureloader.load (); Only files in the form of http:// are accepted, so the image cannot be displayed after packaging. Here, you only need to put your own image on the Tomcat server and fetch the image from your Own Tomcat server

    1. Vue built projects in Google 32-bit version 49 browser cannot be opened

      Why do I keep talking about the Lower version of Google when most people don’t use the lower version anymore, but a small percentage of my clients still use itXPWe can’t make him compatibleIEBut at least Google’s lower version has to be fixed for someone else!

      I don’t know exactly why, but maybe the lower version browser doesn’t get values the same as the higher version, and the solution is innode_modules\three\buildfindthree.module.js Comment out thethis.setSessionThis fetch method

      We still have to find itnode_modules\three-trackballcontrolsIn theindex.jsFile and comment it outconst getMouseOnScreenAs well asconst getMouseOnCircleThese two methods

      As for why I want to comment these methods I have not specifically to study, for the low version of my idea is to solve the problem is OK! Let me know in the comments if anyone knows a solution, Aligardo

      • conclusion

      I have also contacted and learned three.js recently. Most of the content in this article is about the problems encountered in the writing process and the solutions. If you have any problems, please learn from each other as much as possible.