Threejs官例解析,标签&css2dlabel(一)
最近需要给自个儿项目的模型加上标签,来显示一些有的没的的数据。
去例子萌里瞄了一眼,发现了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标签的物体的位置
判断是否在距离以内
判断是否有遮挡
从相机朝着物体发了一个射线,看看有没有遮挡。