Threejs官例解析,标签&css2dlabel(一)

5 篇文章 0 订阅
2 篇文章 0 订阅

最近需要给自个儿项目的模型加上标签,来显示一些有的没的的数据。

去例子萌里瞄了一眼,发现了css2dlabel 这个例子。

远瞅是这样的!其实近瞅也这样,这个顶着一个moon的月亮会绕着这个地球一直旋转,上面的标签也会和他一起移动。(我总感觉应该顶一个奋斗当标签)
在这里插入图片描述
在这里插入图片描述

闲言少叙,来看看咋用的。

调用篇

拿月球举例子吧

生成球。这个瞄一眼应该不会有太大的问题。

在这里插入图片描述
shininess。默认是30,看起来会有明显的高光点。而月球表面那么多石灰一样的东西,太亮不太真,所以调暗点。

下面是30和5的对比,大小应该一样,截屏没控制好。

在这里插入图片描述
在这里插入图片描述
生成标签如下:
在这里插入图片描述

正常生成一个文字为Moon的div,label类里有点样式,不多。要想丰富样式就多改改这里。

重点来了,他生成了一个CSS2DObject类,传入了那个div, 并把它举到了月亮头顶。加到了月亮坐标系。

如果新建了一个三维的无论什么东西,加到月亮里面都是会跟着月亮一起动的。

可是,我们加的是二维的。不在一个维度里。这也是这个例子最有趣的地方。

Init函数里示例化了一个CSS2DRenderer,这个类里面有一个超大的隐藏的div来管理所有的label,这个下面读源码的时候还会分析,先瞄一眼过,应该问题不大。
在这里插入图片描述

调用部分剩下就是,让月亮转转,刷新一下labelRenderer,最后这个才是这盘菜所有的精华。

在这里插入图片描述
核心篇

这里总管两个类,一个CSS2DObject,一个CSS2DRenderer

CSS2DObject 这个类没有太大的用处,基本是用来类型识别,本身也很简单

在这里插入图片描述
用来类型识别

上面的这个监听很有趣,不知道干嘛的,印象里也没有这个监听,难道要自定义使用?类似于下面这样?参考代码来自:这个小哥

window.addEventListener("testEvent", function(obj){
       console.log(obj.data);
       alert("触发成功!")
});
function clickHere() {
     // 创建自定义事件
     var event = document.createEvent("HTMLEvents");
     // 初始化testEvent事件
     event.initEvent("testEvent", false, true);
     event.data = {"click":true};
     // 触发自定义事件
     window.dispatchEvent(event);
 }

查了一下Dom事件也没发现。

CSS2DRenderer类里面负责了主要的操作和更新。

先看一下这个类里面的定义的变量
在这里插入图片描述

前面说过,这个里面会定义一个hidden的大的div,大概就是屏幕的分辨率,来管理所有的label,这里的长宽高都是这个div相关的属性。

vector记录投影变换后,在投影坐标系里面的坐标值。

研究清楚了这个我突然知道CSS2DObject这个类是干啥用的了,里面没有几行代码,我以为只是用来类型区分的,其实不是,我上面说,如果是一个三维物体扔在一个移动的父物体上,这个三维物体是会跟随父物体运动的,所以这货的意义就在于,它把一个label纯二维的东西,其实给包装成了一个三维的物体,让这个三维物体跟着父物体运动,反算这个三维物体的屏幕坐标,也就知道了label的屏幕偏移量。感觉好有趣,果然每个人实现的脑回路都是不一样的。如果是这样计算的话,其实直接计算实体的屏幕坐标也是可以的,比如那个月亮,在给一点二维上的偏移。

继续说这个vector,用到的就3行,还是蛮考验基础的。

在这里插入图片描述
如果一个物体是CSS2DObject的话,就从它的世界矩阵中获取这哥们的世界坐标。

顺便也可以把定义的viewProjectionMatrix变量也解释了。

我的一个 CSS2DObject 获得了世界坐标后,又转换到了相机的坐标系,然后进行了投影变换,可以简单理解为投影到了屏幕。这就是第二行做的事,最后一行,希望的尽量解释清楚。

translate(-50%,-50%) 这个变换如果不加,label偏右偏下,为了让它居中,加了这个。如下:

在这里插入图片描述在这里插入图片描述

后面的translate是为了将投影变换后的坐标,和屏幕的坐标相对应。如下:

在这里插入图片描述
在这里插入图片描述
我们有的是第一个坐标系的坐标,要获得第二个坐标系的,这个映射自己回去导,就是后半段的translate。

八卦一下viewProjectionMatrix矩阵是怎么获得的,
在这里插入图片描述

首先把scene里面的所有的物体撸一遍更一下世界坐标,然后更一下相机的世界坐标,

viewMatrix保存的是从世界坐标系转换到相机坐标系需要的矩阵。

在乘上projectionMatrix,就可以换到投影坐标系了~

(zOrder是自带的函数,rayObj是我自己根据需要加上的)

cache里的 WeakMap() 据说是ES6的新玩具,查了一下,瞄了一眼别人家的文章,主要的特点就以下几个:

key必须是object,且不可枚举,也就是说不可以被遍历。

没有size。

它的出现对垃圾回收友好,就是说没有引用的话可以袅袅悄悄的被回收。因为是“弱指针”。(最近满世界都对垃圾很关注嘛)

这个工程里是这样用滴。
在这里插入图片描述
在这里插入图片描述

还发现了一个好用的函数 scene.traverse 这家伙可以遍历 scene里面所有的物体。

周五没写完,隔了两天在写这篇像喝断片了一样。= =,看来还是要一鼓作气啊~

查了一下源码,发现这是Object3D.prototype里面的函数,大部分的类都是继承自这个类的,也就是说,大部分的实例都有这个函数。源码也不复杂,封装完的代码还是很清爽的。

在这里插入图片描述
因为项目的需要,在原有功能的基础上增加了遮挡隐藏和距离限制显示的功能。

简单来说就是如果地球挡住了月亮,就不显示月亮的标签。

如果在控制相机的过程中,和月球/地球的距离过近或过远都会隐藏标签。

有时候项目的标签不止一两个,甚至可能会很多,这样可以有效突出正在关注的标签。

增加了一个函数rayObj,接受四个参数(scene,camera, near,far)

最后两个如果不传参,相当于没有限制。

在这里插入图片描述
获得相机和含有CSS2DObject标签的物体的位置

在这里插入图片描述
判断是否在距离以内
在这里插入图片描述

判断是否有遮挡

在这里插入图片描述
从相机朝着物体发了一个射线,看看有没有遮挡。

因为有人私信问过我,所以我把疑似当年的CSS2DRenderer.js又翻出来了。给大家一个完整的参考。

/**
 * CSS2DRenderer.js
 */

THREE.CSS2DObject = function ( element ) {

	THREE.Object3D.call( this );

	this.element = element;
	this.element.style.position = 'absolute';

	this.addEventListener( 'removed', function ( event ) {

		if ( this.element.parentNode !== null ) {  

			this.element.parentNode.removeChild( this.element );

		}

	} );
};

THREE.CSS2DObject.prototype = Object.create( THREE.Object3D.prototype );
THREE.CSS2DObject.prototype.constructor = THREE.CSS2DObject; 

//

THREE.CSS2DRenderer = function () {

	console.log( 'THREE.CSS2DRenderer', THREE.REVISION );

	var _width, _height;
	var _widthHalf, _heightHalf;

	var vector = new THREE.Vector3();
	var viewMatrix = new THREE.Matrix4();
	var viewProjectionMatrix = new THREE.Matrix4();

	var cache = {
		objects: new WeakMap()
	};

	var domElement = document.createElement( 'div' );
	domElement.style.overflow = 'hidden';

	this.domElement = domElement;

	this.getSize = function () {

		return {
			width: _width,
			height: _height
		};

	};

	this.setSize = function ( width, height ) {

		_width = width;
		_height = height;

		_widthHalf = _width / 2;
		_heightHalf = _height / 2;

		domElement.style.width = width + 'px';
		domElement.style.height = height + 'px';

	};

	var renderObject = function ( object, camera ) {
		if ( object instanceof THREE.CSS2DObject ) {

			vector.setFromMatrixPosition( object.matrixWorld );			
			vector.applyMatrix4( viewProjectionMatrix );
			var element = object.element;
			var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - vector.y * _heightHalf + _heightHalf ) + 'px)';
			
			element.style.WebkitTransform = style;
			element.style.MozTransform = style;
			element.style.oTransform = style;
			element.style.transform = style;

			var objectData = {
				distanceToCameraSquared: getDistanceToSquared( camera, object )
			};

			cache.objects.set( object, objectData );

			if ( element.parentNode !== domElement ) {

				domElement.appendChild( element );

			}

		}

		for ( var i = 0, l = object.children.length; i < l; i ++ ) {

			renderObject( object.children[ i ], camera );

		}

	};

	var getDistanceToSquared = function () {

		var a = new THREE.Vector3();
		var b = new THREE.Vector3();

		return function ( object1, object2 ) {

			a.setFromMatrixPosition( object1.matrixWorld );
			b.setFromMatrixPosition( object2.matrixWorld );

			return a.distanceToSquared( b );

		};

	}();

	var filterAndFlatten = function ( scene ) {

		var result = [];

		 ( function ( object ) {

			if ( object instanceof THREE.CSS2DObject ) result.push( object );

		} );

		return result;

	};

	var zOrder = function ( scene ) {

		var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {

			var distanceA = cache.objects.get( a ).distanceToCameraSquared;
			var distanceB = cache.objects.get( b ).distanceToCameraSquared;

			return distanceA - distanceB;

		} );

		var zMax = sorted.length;
		for ( var i = 0, l = sorted.length; i < l; i ++ ) {

			//sorted[ i ].element.style.visibility = "visible";
			sorted[ i ].element.style.zIndex = zMax - i;

		}

	};
	var rayObj = function(scene,camera, near,far)
	{
		this.near = near || 0;
		this.far = far || Infinity;
		scene.traverse( function ( object ) {

			if ( object instanceof THREE.CSS2DObject ) 
			{
				let visible = "visible";
				let posCam = new THREE.Vector3().setFromMatrixPosition( camera.matrixWorld );
				let posMesh = new THREE.Vector3().setFromMatrixPosition( object.parent.matrixWorld );
				
				// 判断是否在距离内
				let dis = posCam.clone().distanceTo(posMesh);
				if(visible == "visible")
				{
					if(dis < this.near || dis > this.far)
					{
						visible = "hidden"; 
					}
				}
				
				// 判断是否有遮挡
				if(visible == "visible")
				{
					let dir = posMesh.clone().sub(posCam).normalize();
					let raycaster = new THREE.Raycaster(posCam, dir,camera.near,camera.far);
					let intersects = raycaster.intersectObjects( scene.children );
				
					for(let i = 0; i < intersects.length; i++)
					{
						if(intersects[i].object instanceof THREE.AxesHelper)
						{
							continue;
						}
						if(intersects[i].object == object.parent)
						{
							continue;
						}
						visible = "hidden";
					}
				}
				object.element.style.visibility = visible;

			}

		} );
	};
	this.render = function ( scene, camera ) {

		scene.updateMatrixWorld(); 

		if ( camera.parent === null ) camera.updateMatrixWorld();

		viewMatrix.copy( camera.matrixWorldInverse );
		viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix );

		renderObject( scene, camera );
		//zOrder( scene );
		rayObj(scene,camera);

	};

};

css2d_label.html

<!DOCTYPE html>
<html>
	<head>
		<meta charset="utf-8">
		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
		<title>three.js css2d - label</title>
		<style>
			body {
				background-color: #000;
				margin: 0;
				overflow: hidden;
			}
			#info {
				position: absolute;
				top: 0px;
				width: 100%;
				color: #FFF;
				padding: 5px;
				font-family: Monospace;
				font-size: 13px;
				text-align: center;
				z-index: 1;
			}

			.label{
				width: 80px;
				height: 30px;
				text-align: center;
				color: #FFF;
				font-family: sans-serif;
				padding: 2px;
				background: url("textures/labelBg.png");
			    background-size: 80px 32px;
			}

			a {
				color: #000000;
			}

		</style>
	</head>
	<body>
		<div id="info"><a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - three.js css2d - label</div>
		<div id="tets"></div>
		<script src="threejs/three.js"></script>

		<script src="threejs/OrbitControls.js"></script>

		<script src="threejs/CSS2DRenderer.js"></script>

		<script>
		

			var camera, scene, renderer, labelRenderer;

			var clock = new THREE.Clock();
			var textureLoader = new THREE.TextureLoader();

			var earth, moon;

			init();
			animate();

			function init() {

				var EARTH_RADIUS = 1;
				var MOON_RADIUS = 0.27;

				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 1000 );

				camera.position.set( 10, 5, 20 );

				var controls = new THREE.OrbitControls( camera );

				scene = new THREE.Scene();
				scene.background = new THREE.Color( 0x333333 );

				var dirLight = new THREE.DirectionalLight( 0xffffff );
				dirLight.position.set( 0, 0, 1 );
				scene.add( dirLight );

				var axesHelper = new THREE.AxesHelper( 5 );

				scene.add( axesHelper );

				//

				var earthGeometry = new THREE.SphereBufferGeometry( EARTH_RADIUS, 32, 32);
				var earthMaterial = new THREE.MeshPhongMaterial( {
					specular: 0x333333,
					shininess: 5,
					map: textureLoader.load( 'textures/planets/earth_atmos_2048.jpg' ),
					specularMap: textureLoader.load( 'textures/planets/earth_specular_2048.jpg' ),
					normalMap: textureLoader.load( 'textures/planets/earth_normal_2048.jpg' ),
					normalScale: new THREE.Vector2( 0.85, 0.85 )
				} );
				earth = new THREE.Mesh( earthGeometry, earthMaterial );
				earth.name = "earth";
				scene.add( earth );

				var moonGeometry = new THREE.SphereBufferGeometry( MOON_RADIUS, 16, 16 );
				var moonMaterial = new THREE.MeshPhongMaterial( {
					shininess: 5,
					map: textureLoader.load( 'textures/planets/moon_1024.jpg' )
				} );
				moon = new THREE.Mesh( moonGeometry, moonMaterial );
				moon.position.set(10,0,0);
				moon.name = "moon";
				scene.add( moon );

				//

				var earthDiv = document.createElement( 'div' );
				earthDiv.className = 'label';
				earthDiv.textContent = 'Earth';
				earthDiv.style.marginTop = '-1em';
				var earthLabel = new THREE.CSS2DObject( earthDiv );
				earthLabel.position.set( 0, EARTH_RADIUS, 0 );
				earth.add( earthLabel );

				var moonDiv = document.createElement( 'div' );
				moonDiv.className = 'label';
				moonDiv.textContent = 'Moon';
				moonDiv.style.marginTop = '-1em';
				var moonLabel = new THREE.CSS2DObject( moonDiv );
				moonLabel.position.set( 0, MOON_RADIUS, 0 );
				moon.add( moonLabel );

				//

				renderer = new THREE.WebGLRenderer();
				renderer.setPixelRatio( window.devicePixelRatio );
				renderer.setSize( window.innerWidth, window.innerHeight );
				document.body.appendChild( renderer.domElement );

				labelRenderer = new THREE.CSS2DRenderer();
				labelRenderer.setSize( window.innerWidth, window.innerHeight );
				labelRenderer.domElement.style.position = 'absolute';
				labelRenderer.domElement.style.top = 0;
				document.body.appendChild( labelRenderer.domElement );

			}

			function animate() {

				requestAnimationFrame( animate );

				var elapsed = clock.getElapsedTime();

				moon.position.set( Math.sin( elapsed ) * 5, 0, Math.cos( elapsed ) * 5 );

				renderer.render( scene, camera );
				labelRenderer.render( scene, camera );

			}

		</script>
	</body>
</html>

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值