说明:本文档基于最新版的Three.js(r94),由于以前从来没有写过JavaScript和HTML,所以有不足之处还望见谅。
1.准备
1.1.官网 https://threejs.org/
1.2.源码及示例 https://github.com/mrdoob/three.js/
2.HelloWorld
var camera, scene, controls, renderer;
init();
animate();
function init() {
// camera
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.01, 10000);
camera.position.set(0, 50, 500);
// scene
scene = new THREE.Scene();
scene.background = new THREE.Color(0xf0f0f0);
// light
var light = new THREE.DirectionalLight(0xffffff);
light.position.set(0, 0, 1);
scene.add(light);
//Add a Cube
var cube = new THREE.Mesh(new THREE.BoxGeometry(50, 100, 20), new THREE.MeshBasicMaterial({color: 0xff0000}));
cube.name = "Cube";
cube.rotation.y = Math.PI / 4;
cube.rotation.z = Math.PI / 4;
scene.add(cube);
// renderer
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
}
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
3.基本概念
Three中基本概念有以下几个,渲染器(Renderer),场景(Scene),相机(Camera)和灯光(Lights)。
3.1.Renderer:
3D渲染器,通过循环调用animate方法来实现绘图。
3.1.1.创建
renderer = new THREE.WebGLRenderer({antialias: true});
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
3.1.2.加入HTML
document.body.appendChild(renderer.domElement);
3.1.3.antialias属性,抗锯齿效果设为有效,设为TRUE会影响性能。
3.1.4.animate()循环调用渲染器的render方法,进行绘图。
function animate() {
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
3.1.5.大小设置
一般情况下,Renderer的大小设置为父标签的innerWidth和innerHeight。
3.2.Scene
场景,也就是一个三维空间,所有的图形以及辅助工具都会加入其中。
3.2.1.创建
scene = new THREE.Scene();
3.2.2.添加图形
添加一个立方体的简单的示例:
var cube = new THREE.Mesh(new THREE.BoxGeometry(50, 100, 20), new THREE.MeshBasicMaterial({color: 0xff0000}));
cube.name = "Cube";
cube.rotation.y = Math.PI / 4;
cube.rotation.z = Math.PI / 4;
scene.add(cube);
3.3.Camera
相机,用来控制三维空间中物体的投影,以及我们在屏幕上看到的内容。
3.3.1.分类
- 透视投影:THREE.PerspectiveCamera,遵循近大远小规则,跟现实生活中我们看物体的方式是一样的。
- 正投影:THREE.OrthographicCamera,不论远近,按照统一的大小进行投影。
3.3.2.坐标系
通常使用右手坐标系统,也就是我们数学几何中常用的坐标系。
3.4.Lights
光源大致分为8种:
- AmbientLight: 环境光 ;它的颜色会添加到整个场景和所有对象的当前颜色上
- PointLight: 点光源;空间中的一点,朝所有的方向发射光线
- SpotLight: 聚光灯光源;聚光灯是由点光源发出,这种类型的光也可以产生投影,有聚光的效果
- DirectionalLight:方向光,平行光;例如:太阳光
- HemisphereLight:半球光光源 ;可以为室内场景创建更加自然的光照效果,模拟反光面和光线微弱的天气
- AreaLight:面光源; 使用这种光源可以指定散发光线的平面,而不是空间的一个点
- RectAreaLight:矩形区域光源;这种光从一个矩形面均匀地发射,可以用来模拟明亮的窗户或者带状的照明;可以产生投影
- LensFlare:镜头炫光;这不是一种光源,但是通过该炫光可以为场景中的光源添加炫目的效果
4.Mesh,Object3D
Mesh用来创建和初始化物体,通常情况下mesh有两个重要参数:形状(Geometry)和材质(Material)。
var geometry = new THREE.BoxGeometry(50, 100, 20);
var material = new THREE.MeshBasicMaterial({color: 0xff0000});
var mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
4.1.Geometry
三维空间中的几何体,立方体,球体等。
- BoxGeometry: 立方体
- CylinderGeometry:圆柱体
- SphereGeometry:球体
- TorusGeometry:圆环
- 其它
4.2 Material
- MeshBasicMaterial:为几何体赋予一种简单的颜色,或者显示几何体的线框
- MeshDepthMaterial:根据网格到相机的距离,该材质决定如何给网格染色
- MeshNormalMaterial:根据物体表面的法向量计算颜色
- MeshFaceMaterial:这是一种容器,可以在该容器中为物体的各个表面上设置不同的颜色
- MeshLambertMaterial:考虑光照的影响,可以创建颜色暗淡,不光亮的物体
- MeshPhongMaterial:考虑光照的影响,可以创建光亮的物体
- ShaderMaterial:使用自定义的着色器程序,直接控制顶点的放置方式,以及像素的着色方式。
- LineBasicMaterial:可以用于THREE.Line几何体,从而创建着色的直线
- LineDashedMaterial:类似与基础材质,但可以创建虚线效果
4.3.Rotation
物体的旋转
- X
mesh.rotation.x = Math.PI / 2;//以X轴为轴旋转90°
- Y
mesh.rotation.y = Math.PI / 2;//以Y轴为轴旋转90°
- Z
mesh.rotation.z = Math.PI / 2;//以Z轴为轴旋转90°
4.4.Translate
- X
mesh.translateX(100);//沿X轴移动100.(向右移动)
- Y
mesh.translateY(100);//沿Y轴移动100.(向上移动)
- Z
mesh.translateZ(100);//沿Z轴移动100.(向屏幕外移动)
5.Geometry
5.1.Geometry和BufferGeometry
能够存储顶点(vertex),位置(positions),面(faces),颜色(colors)等。
- Geometry 生成的模型是这样的 (代码)-> (CUP 进行数据处理,转化成虚拟3D数据) -> (GPU 进行数据组装,转化成像素点,准备渲染) -> 显示器。第二次操作时重复走这些流程。
- BufferGeometry 生成模型流程 (代码) -> (CUP 进行数据处理,转化成虚拟3D数据) -> (GPU 进行数据组装,转化成像素点,准备渲染) -> (丢入缓存区) -> 显示器 第二次修改时,通过API直接修改缓存区数据,流程就变成了这样 (代码) -> (CUP 进行数据处理,转化成虚拟3D数据) -> (修改缓存区数据) -> 显示器。这样就节约了GPU性能的运算性能。
- 通常情况下使用vertices属性来初始化Geometry的点。
geometry.vertices.push( new THREE.Vector3( -10, 10, 0 ), new THREE.Vector3( -10, -10, 0 ), new THREE.Vector3( 10, -10, 0 ) );
- Geometry和BufferGeometry的转换
var geometry = new THREE.Geometry().fromBufferGeometry(bufferGeometry);
- BBox of Geometry Geometry.boundingBox通常需要先调用Geometry.computeBoundingBox()方法来生成。
- BufferGeometry的BBox可以通过转成Geometry然后从Geometry中取得。
5.2.ExtrudeGeometry和ExtrudeBufferGeometry
这两个Geometry主要用于对普通的形状(通常是2D的)进行拉伸,使用方法很合其它类似。但需要先了解Shape和ShapePath。 ExtrudeGeometry继承自Geometry。 ExtrudeBufferGeometry继承自BufferGeometry。
6.Shape,ShapePath和Path
6.1.Shape
在Three中可以用Shape来定义任意形状的2D图形,通常这些图形都是由一些点来形成的,这些形状可以很好的保留洞(holes),Shape可以在ShapeGeometry和BufferGeometry中使用。
var heartShape = new THREE.Shape();
heartShape.moveTo(25, 25);
heartShape.bezierCurveTo(25, 25, 20, 0, 0, 0);
heartShape.bezierCurveTo(30, 0, 30, 35, 30, 35);
heartShape.bezierCurveTo(30, 55, 10, 77, 25, 95);
heartShape.bezierCurveTo(60, 77, 80, 55, 80, 35);
heartShape.bezierCurveTo(80, 35, 80, 0, 50, 0);
heartShape.bezierCurveTo(35, 0, 25, 25, 25, 25);
var extrudeSettings = {
amount: 8,
bevelEnabled: true,
bevelSegments: 2,
steps: 2,
bevelSize: 1,
bevelThickness: 1
};
var geometry = new THREE.ExtrudeGeometry(heartShape, extrudeSettings);
var mesh = new THREE.Mesh(geometry, new THREE.MeshPhongMaterial());
6.2.ShapePath
ShapePath通常可以将一组Shape转换成Path,或者我们可以从ShapePath中自动切出所有的Shape,例如将SVG中的Path转换成Three中可以渲染的Shape等。
6.3.Path
Path是2D Canvas中常用的概念,保存用于创建2D图形的路径。
var path = new THREE.Path();
path.lineTo( 0, 0.8 );
path.quadraticCurveTo( 0, 1, 0.2, 1 );
path.lineTo( 1, 1 );
var points = path.getPoints();
var geometry = new THREE.BufferGeometry().setFromPoints( points );
var material = new THREE.LineBasicMaterial( { color: 0xffffff } );
var line = new THREE.Line( geometry, material );
scene.add( line );
7.Three其它设置
7.1.OrbitControls
用来通过鼠标的移动拖拽实现相机(Camera)的旋转与缩放。 添加的方法很简单:
controls = new THREE.OrbitControls(camera, renderer.domElement);
controls.rotateSpeed = 0.5;
controls.minDistance = 100;
controls.maxDistance = 6000;
- rotateSpeed:旋转的速度
- minDistance:透视图相机所能到达的最近距离
- maxDistance:透视图相机所能到达的最远距离 注意:OrbitControls需要在animate方法中调用update更新。
function animate() {
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
7.2.GridHelper
一个绘制辅助格子的工具。
var grid = new THREE.GridHelper(1000, 10);
// helper.rotation.x = Math.PI / 2;
scene.add(grid);
GridHelper( size : number, divisions : Number, colorCenterLine : Color, colorGrid : Color )
- size:格子的大小,默认为10。
- divisions:分成多少格,默认为10.
- colorCenterLine:中心线的颜色。
- colorGrid:格子的颜色。
7.3.AxisHelper
显示坐标轴,方便调试。
var axes = new THREE.AxesHelper(100);
scene.add(axes)
- size: 坐标轴标示线的长度。
7.4.Raycaster
用来实现3D对象和鼠标交互。
raycaster = new THREE.Raycaster();
详细请参考鼠标交互。
7.5.Group
分组,可以将一些3D模型放到Group里面方便管理。
8.SVG转Three
8.1.基本概念
SVG通常是一个标准的XML文件,常用的节点如rect,polygon和path等。Three能够支持将这些标准的标签一一转成Three的ShapePath,进而转成Shape,然后进行拉伸(extrude)和材料填充渲染到WebGL中。
8.2.SVG文件加载
var loader = new THREE.SVGLoader();
loader.load(
SVG_URL, //SVG文件的URL
function (shapePaths) {
// 处理加载的ShapePath,转Shape,创建Mesh等。
},
// 加载进度
function (xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// 异常处理
function (error) {
console.log('An error happened' + error);
}
);
SVGLoader做了哪些事情:
- 遍历SVG DOM。
- 识别的SVG标签:path,rect,polygon,polyline,circle,ellipse和line。 SVG标签加载限制:首先会从各个节点中读取fill值,只有fill值不为空或不透明才会加载。
function isVisible( style ) { return style.fill !== 'none' && style.fill !== 'transparent'; }
- 识别的SVG标签:path,rect,polygon,polyline,circle,ellipse和line。 SVG标签加载限制:首先会从各个节点中读取fill值,只有fill值不为空或不透明才会加载。
- 只有这些标签可识别,并且可见,SVGLoader才会创建完全对应的ShapePath。
重要的一点:由于这个SVGLoader只能读取标准的SVG文件。 通常情况下,在Adobe Illustrator中创建的SVG文件不能直接被加载,需要做一下转换。File -> Export -> Export As… -> 在打开的对话框中选择SVG格式 -> Export。
8.3.SVG Path(shapes)转Three Shape
通过THREE.SVGLoader加载SVG格式的图片,会自动将一些标准格式的SVG图形转成ShapePath。接下来就要将这些ShapePath转成Three的Shape然后加到Scene中显示。
8.3.1.ShapePath转Shape
var shapes = path.toShapes(true, false);
8.3.2.拉伸Shape,创建ExtrudeBufferGeometry
var bufferGeometry = new THREE.ExtrudeBufferGeometry(shapes, {depth: depth});
注意这里的参数depth就是你要拉伸的值,也就是Z轴方向的高度。
8.3.3. 通过ShapePath中加载的SVG图形的颜色创建Material
var material = new THREE.MeshBasicMaterial({color: color});
简单情况下,我们可以创建一个基础的Material去加载SVG中的颜色属性。
8.3.4. 创建Mesh加入Scene中显示
var mesh = new THREE.Mesh(geometry, material);
8.4.文本处理
文本处理在3D中特别麻烦,尤其是中文的处理。虽然在设计SVG的时候可以将文本转成Path,这些在浏览器中显示不会有什么问题。但是在Three中,万一一个文本所对应的path生成的Shape中,有一些的depth(拉伸)值设置的不一样,文本就会显示成很诡异的样子。 所以可以建议将所有的文本值过滤掉。参考使用Sprite去实现。
8.5.拉伸高度
SVG中有些图会默认先在底层创建一个rect,然后将所有的图再画在这个rect上。就像我们的室内地图的地板一样。 在做拉伸的时候,必须要用很小的depth值去渲染这些大面积的图形,或者直接过滤掉。
8.6.Shape合并处理
8.6.1. 一个ShapePath通常可以生成多个Shape,我们可以遍历这个Shape数组,将Shape一个一个创建ExtrudeBufferGeometry和Mesh加入到Scene中,也可以将这些所有的Shape作为一个整体去创建ExtrudeBufferGeometry和Mesh加入到Scene中。当然了,创建的Mesh越多,WebGL渲染的性能就越差。为了提高性能,通常将一个ShapePath对应的所有的Shape创建成一个ExtrudeBufferGeometry去实现。
8.6.2. 通过Geometry类可以取到BBox,进而取得center和size值。
8.7.中心点设置
8.7.1. Scene加入的所有图形,其位置(positions)默认都是在(0,0,0)点,也就是Renderer的中心。
8.7.2. 我们通常会使用地板(或SVG中最底层能包含所有图形的那个图形)来确定移动的多少。详细请参考下一小节。
8.8.和原始的SVG图片方向保持一致。
由于SVG中的坐标点事基于计算机坐标系统(自上而下,从左往右)的原则,而3D中(Three也是一样)使用的是前面提到过的右手坐标系统,由此可见,X轴基本一致,但Y轴是完全相反的。所以首先要做的,就是把X轴旋转180°。
8.8.1. 将X轴旋转180°,使得Y坐标的值能够保持一致。
mesh.rotation.x = Math.PI;
8.8.2. 由于我们将X轴旋转了180°,导致Z坐标的值反了过去,Z坐标的值,也就是我们设定的拉伸值(depth),所以我们需要将Z坐标的值向相反的方向拉回来。
mesh.translateZ(-depth - 1);
8.8.3. 移动X坐标的值使得整个SVG渲染图到原点。
mesh.translateX(offsetX);
8.8.4. 移动Y坐标的值使得整个SVG渲染图到原点。
mesh.translateY(offsetY);
8.8.5. 计算X轴和Y轴的偏移量。 X轴和Y轴的偏移量,可以通过原始SVG图片的ViewBox来确定,但这种方式作出来不太精确。我暂时使用的方法是遍历所有的ShapePath(这些ShapePath是SVGLoader中加载得来的)中的Shape,计算出一个最大的能包含所有图形的矩形区域,然后再进行X轴和Y轴的位移。
8.9. 使用Group来批量设置
var box = new THREE.Box3();
box.setFromObject(group);
var center = box.getCenter();
for(var i=0;i<group.children.length;i++) {
group.children[i].rotation.z = Math.PI;
group.children[i].rotation.y = Math.PI;
group.children[i].position.x = -center.x;
group.children[i].position.y = center.y;
}
因为所有的模型都加到了一个Group中,我们可以通过Group的中心点来设置每一个模型的旋转和位移,但这段代码还不太完善。需要将所有图形的depth通过Z轴拉回来。
9. 鼠标交互
Three模型与鼠标的交互,用到的是Raycaster,前面已经提到怎么添加了,接下来会说明怎么找到鼠标所对应的Three模型。
9.1. 窗口坐标转成Three坐标。
var mouse = new THREE.Vector2();
function onMouseMove( event ) {
// calculate mouse position in normalized device coordinates
// (-1 to +1) for both components
mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
}
9.2. 查找交互的Three模型。
function render() {
// update the picking ray with the camera and mouse position
raycaster.setFromCamera( mouse, camera );
// calculate objects intersecting the picking ray
var intersects = raycaster.intersectObjects( scene.children );
for ( var i = 0; i < intersects.length; i++ ) {
intersects[ i ].object.material.color.set( 0xff0000 );
}
renderer.render( scene, camera );
}
window.addEventListener( 'mousemove', onMouseMove, false );
window.requestAnimationFrame(render);
其实,主要的内容就2个:
- 基于转换后的鼠标值和相机(Camera)更新raycaster。
- 通过raycaster查找给定的元素中的交互值。
- 给定的元素(scene.children)也可以是你任意的想要被交互的模型的数组。
- 找到的模型也是一个数组,需要遍历进而找到合适的。
10. Sprite
Sprite通常在添加浮标,纹理等的时候使用。
var iconSprite = new THREE.Sprite(new THREE.SpriteMaterial({map: new THREE.TextureLoader().load(path)}));
iconSprite.position.copy(intersected.point);
iconSprite.positionZ = iconSprite.positionZ + 150;
iconSprite.scale.x = iconSprite.scale.y = 20;//100;
scene.add(iconSprite);
- Path是用来在Sprite上显示的图片的路径。
- 位置设置中interescted是通过raycaster找到的鼠标点击时的模型。
- 其它的设置和Geometry Mesh的设置一样。
11. BoundingBox
BoundingBox简称BBox,是用来界定物体的几何图元的矩形边界框,跟2D中的bounds相似,都是用来描述物体或图形的大小和位置。 BBox是Three中Geometry的一个属性,可以通过以下方法来获取:
geometry.computeBoundingBox();
return geometry.boundingBox;
根据取到的BBox,我们进而可以取到物体的中心点和大小。
- Center:物体的中心点,由于这个点是从这个物体的BBox中取出的,它其实就代表了这个物体在Three坐标系中的位置。这个和position属性要分清,所有的物体的position在默认情况下都是(0,0,0),只有在调用translateX/Y/Z()方法之后,这和值才会发生改变。详细请参考第13节。
- Size:物体的三维大小。
另外需要注意的是:由于BBox只能相对于物体的几何信息,所以只能从Geometry中取得,如果想要从Mesh或是BufferGeometry中取得BBox,需要做一定的转换。
function getBox3(object) {
if (object instanceof THREE.Mesh) {
return getBox3(object.geometry);
} else if (object instanceof THREE.BufferGeometry) {
var geometry = new THREE.Geometry().fromBufferGeometry(object);
return getBox3(geometry);
} else if (object instanceof THREE.Geometry) {
object.computeBoundingBox();
return object.boundingBox;
}
console.log("Unsupport Bbox3 for: " + object);
return null;
}
获取Center和Size的代码如下:
function getSize(object) {
if (object instanceof THREE.BufferGeometry) {
var geometry = new THREE.Geometry().fromBufferGeometry(object);
return getSize(geometry);
} else if (object instanceof THREE.Mesh) {
var box = new THREE.Box3();
box.setFromObject(object);
return box.getSize(new THREE.Vector3());
} else if (object instanceof THREE.Geometry) {
object.computeBoundingBox();
return object.boundingBox.getSize(new THREE.Vector3());
}
return null;
}
function getCenter(object) {
if (object instanceof THREE.BufferGeometry) {
var geometry = new THREE.Geometry().fromBufferGeometry(object);
return getCenter(geometry);
} else if (object instanceof THREE.Geometry) {
object.computeBoundingBox();
return object.boundingBox.getCenter(new THREE.Vector3());
} else if (object instanceof THREE.Mesh) {
var box = new THREE.Box3();
box.setFromObject(object);
return box.getCenter(new THREE.Vector3());
}
return null;
}
12. Path-Finding
寻路是3D地图或游戏中常用的概念,其大部分是通过NavigationMesh的方式实现。在一些3D建模工具如Unity等中,可以基于3D模型直接生成一些NavigationMesh模型,进而使用算法(如Astar)等来实现寻路。 由于我们的模型是基于SVG自动生成(伪3D,我们也可以称为2.5D),以前已经有现成的Grid用于寻路,我们可以用Grid加Astar来实现寻路。
12.1. Astar
http://github.com/bgrins/javascript-astar
12.2. 动态生成网格
- 寻找基层(最底层,能包含所有图形的层,也可以叫做地板)。
- 根据基层的大小初始化网格,并设置网格大小。
- 遍历Three模型,将所有的模型在网格中标记成墙。
- 根据前面提到关于BBox的内容,在生成网格时我们可以只根据BBox的信息取到一个二维的矩形来生成网格中的墙。
function getGridRect(mesh) {
if (!(mesh instanceof THREE.Mesh)) {
return new Rect(0, 0, 0, 0);
}
var center = getCenter(mesh);
var size = getSize(mesh);
var rect = new Rect(
center.x - size.x / 2 + floorInfo.width / 2, // Reverse to original location
center.y - size.y / 2 + floorInfo.height / 2,
size.x,
size.y
)
// Rotate y axes.
return reverseRectY(rect, floorInfo.height);
}
/*
* Reverse the rect from it's parent's center
*
* height is the parent's total height value.
*
* it means to rotate 180 degree of the parent through X axes.
* */
function reverseRectY(rect, height) {
return new Rect(rect.x, height - (rect.y + rect.height), rect.width, rect.height)
}
还记得前面提过的,SVG转Three之后,由于坐标系的不同,虽然X值是相等的,但是Y值是相反的,为了能将各个物体在网格中成功的映射,我们需要将各个物体需要以地板(floor)的中心点上下颠倒。
12.3.通过3D对象获取对应网格
- 标记起点和终点。
- 基于标注的起点和终点找到对应的网格。
- 以后连接2D实现中的信息,找到出口,入口对应的网格。
12.4. 调用Astar算法寻路
var result = astar.search(graph, startGrid, endGrid);
12.5. 网格转Three坐标
坐标转换永远都是最重要的问题。网格的坐标同样也是基于窗口坐标(自上而下,从左往右),所以转换的时候要特别注意。
- 网格(GraphNode)的x值是第几行,也就是要转成Y坐标的值。
- 网格(GraphNode)的y值是第几列,也就是要转成X坐标的值。
- 由于网格是基于地板层(基层)的,所以在转换坐标时一种方法是从地板的大小,领一种方法是忽略地板,跟SVGLoader加载之后一样偏移加旋转X轴。 方法一:
var points = [];
var tz = DEFAULT_EXTRUDE_DEPTH * 3; // 最终Line的Z值
for (var i = 0; i < result.length; i++) {
var node = result[i];
var point2d = {
//Line的X值,整体向左移动,到达floor的中心点。
x: node.y * cellWidth + cellWidth / 2 - floorInfo.width / 2,
//Line的Y值,先整体向上移动到达floor的中心点,然后再把翻转。
y: floorInfo.height - (node.x * cellHeight + cellHeight / 2 + floorInfo.height / 2)
};
var point3d = new THREE.Vector3(point2d.x, point2d.y, tz);
points.push(point3d);
}
pathBlueLine = buildLine(pointsBlue, 0x0000ff);
// Create line/
var geometry = new THREE.Geometry();
for (var i = 0; i < points.length; i++) {
geometry.vertices.push(points[i]);
}
var material = new THREE.LineBasicMaterial({
color: 0x0000ff
});
var line = new THREE.Line(geometry, material);
scene.add(line);
方法二:
var points = [];
for (var i = 0; i < result.length; i++) {
var node = result[i];
//The point on floor area.
var point = new THREE.Vector3(
(node.y * cellWidth + cellWidth / 2), //x
(node.x * cellHeight + cellHeight / 2), //y
DEFAULT_EXTRUDE_DEPTH //z
);
points.push(point);
}
var geometry = new THREE.BufferGeometry().setFromPoints(points);
var material = new THREE.LineBasicMaterial({
color: 0x00ff00
});
pathGreenLine = new THREE.Line(geometry, material);
//沿X轴旋转180°,使y值对应
pathGreenLine.rotation.x = Math.PI;
pathGreenLine.translateZ(-DEFAULT_EXTRUDE_DEPTH * 3); //reverse it with depth.
//下面2句,移动到地板的中心
pathGreenLine.translateX(-floorInfo.width / 2);
pathGreenLine.translateY(-floorInfo.height / 2);
pathGreenLine.name = "path-finding";
scene.add(pathGreenLine);
12.6.LineWidth
由于Three中line的宽度在很多浏览器中支持的不是很好。我们可以借助 THREE.MeshLine 来画粗一点的线条。
var geo = new THREE.Geometry();
var tz = DEFAULT_EXTRUDE_DEPTH * 2;
for (var i = 0; i < result.length; i++) {
var node = result[i];
var point2d = {
x: node.y * cellWidth + cellWidth / 2 - floorInfo.width / 2,
y: floorInfo.height - (node.x * cellHeight + cellHeight / 2 + floorInfo.height / 2)
};
var point3d = new THREE.Vector3(point2d.x, point2d.y, tz);
geo.vertices.push(point3d);
}
var g = new MeshLine();
g.setGeometry(geo);
var material = new MeshLineMaterial( {
useMap: false,
color: new THREE.Color(0x0000ff),
opacity: 1,
resolution: resolution,
sizeAttenuation: !false,
lineWidth: 5,
near: camera.near,
far: camera.far
});
pathFatLine = new THREE.Mesh( g.geometry, material );
scene.add(pathFatLine);
13. Object3d.position
function getPosition(object) {
var position = new THREE.Vector3();
if (object instanceof THREE.Mesh) {
object.updateMatrixWorld(true);
position.setFromMatrixPosition(object.matrixWorld);
}
return position;
}
和
var position = mesh.position;
和
var position3 = mesh.matrixWorld.getPosition();
取到的都是一样的值,这个是相对于它自己的一个位置(Object3D.position of objects are local to theirselves)。
var position4 = mesh.position.clone().project(camera);
这个取到的是通过Camera投影到地面的位置。
var p1 = new THREE.Vector3(25, 15, 9); //3D原始坐标
var p2 = p1.clone().project(camera); //3D投影到Camera的坐标,【-1, 1】
var p3 = p2.clone().unproject(camera); //3D投影坐标反投影,这时:p1 == p3
var p4 = new THREE.Vector3((p2.x + 1) / 2 * window.innerWidth, -(p2.y - 1)/2 * window.innerHeight); //将3D坐标转成2D屏幕坐标
var p5 = new THREE.Vector3(p4.x * 2 / window.innerWidth - 1, 1 - p4.y * 2 / window.innerHeight, p2.z); //将2D坐标转成Camera投影坐标,这时:p2 == p5, [-1, 1]
var p6 = p5.clone().unproject(camera); //3D投影坐标反投影,这时:p1 == p3 == p6
- p1 == p3 == p6:原始3D坐标
- p2 == p5:Camera投影坐标
- p4: 屏幕2D坐标。
14. 示例代码
- 在线演示:https://ecsoya.github.io/SVG-Three-Map/
- 练习:https://ecsoya.github.io/SVG-Three-Map/demo/
- 源码:https://github.com/ecsoya/SVG-Three-Map.git