AngryRED

Easier & Better

Three.js开发总结

20 Jul 2018 » js

说明:本文档基于最新版的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';
      }
      
  • 只有这些标签可识别,并且可见,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. 示例代码

15. 参考资料

Related Posts