分享three.js和cannon.js构建Web 3D场景

使用 three.js,您不再需要花哨的游戏PC或控制台来显示逼真的3D图形。 您甚至不需要下载特殊的应用程序。现在每个人都可以使用智能手机和网络浏览器体验令人惊叹的3D应用程序。

这个惊人的库和充满活力的社区是您在浏览器、笔记本电脑、平板电脑或智能手机上创建游戏、 音乐视频、科学和数据可视化或几乎任何您能想象的任何东西所需要的一切!

可在任何操作系统和设备(从智能手机到笔记本电脑到智能手表) 上运行的令人惊叹的、专业品质的、高性能的3D Web应用程序所需的一切, 即使您对Web开发和计算机图形完全陌生。three.js是有史以来最易于访问的计算机图形框架, 我们将充分利用它来引导您很快的获得高水平的专业知识。了解基本的three.js应用程序所需的所有基本概念,有了这些知识, 您将可以立即创建自己的惊人项目。有了三维的力量,唯一的限制就是你的想象力!

引入three.js和cannon.js依赖

<script src="https://cdn.bootcdn.net/ajax/libs/three.js/109/three.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script>

前置知识点

JavaScript的原型和原型链实现类似强类型语言的继承功能

function EventDispatcher() {}
function Object3D() {}
Object3D.prototype = Object.assign(Object.create( EventDispatcher.prototype ), {constructor: Object3D}
);

three.js大量使用了上面这种继承方式,其主要原理是通过Object.create创建一个对象,对象的__proto__属性指向EventDispatcher的原型链。

Object.create(EventDispatcher.prototype )

然后通过 Object.assign方法把扩展属性进行合并;注意这里的constructor,这里把Object3D的构造函数也合并起来,放在Object3D.prototype的原型链上。不然在Object3D.prototype赋值的时候,构造函数就会找不到。

three.js中使用的是右手坐标系,原因是WebGL默认的就是这种坐标系。简单理解就是,x轴正方向向右,y轴正方向向上,z轴正方向由屏幕从里向外。在场景中所有的物体在容器的位置都是依靠这个坐标系设置的。

一般使用的坐标系是当你面朝计算机屏幕时, X轴 是水平的(正方向为右), Y轴 是垂直的(正方向为上), Z轴 垂直于屏幕(正方向为外),这个坐标系也被称为 右手坐标系 。之所被称为 右手坐标系 是因为它是通过如图所示的 右手手势 确定的,即当你伸出右手摆出如图所示手势时, 拇指 指向 X轴的正方向 , 食指 指向 Y轴的正方向 , 中指 指向 Z轴的正方向 ,这种确定坐标系方式也被称为 右手定则 。

场景scene ,它就相当于一个大容器,我们需要展示的所有物体都要放入场景。它又被称为场景图,因为它是一个树形结构数据。能放入场景中的对象都继承了Object3D对象,所以每一个子节点都有自己的局部空间。简单理解就是场景中有一个空间可以添加组、Object3D、网格等物体类型,子节点也是一个小容器,同样可以添加组、Object3D、网格等物体类型。区别就是,子节点设置的坐标位置是相对于父节点的局部空间坐标来改变的。

建立几何体时通过指定几何体的顶点和三角形的面确定了几何体的形状,另外还需要给几何体新增皮肤才能实现物体的效果,材质就像物体的皮肤,决定了物体的质感。

基础材质:以简单着色方式来绘制几何体的材质,不受光照影响。

深度材质:按深度绘制几何体的材质。深度基于相机远近端面,离近端面越近就越白,离远端面越近就越黑。

法向量材质:把法向量对映到RGB颜色的材质。

Lambert材质:是一种需要光源的材质,非光泽表面的材质,没有镜面高光,适用于石膏等表面粗糙的物体。

Phong材质:也是一种需要光源的材质,具有镜面高光的光泽表面的材质,适用于金属、漆面等反光的物体。

材质捕获:使用储存了光照和反射等信息的贴图,然后利用法线方向进行取样。优点是可以用很低的消耗来实现很多特殊风格的效果;缺点是仅对于固定相机视角的情况较好。

下图是使用不同贴图实现的效果:

透视相机近大远小,同样大小的物体离相机近的在画面上显得大,离相机远的物体在画面上显得小。透视相机的视锥体如上图左侧所示,从近端面到远端面构成的区域内的物体才能显示在影象上。

正交相机无论物体距离相机远或者近,在最终渲染的图片中物体的大小都保持不变。正交相机的视锥体如下图右侧所示,和透视相机一样,从近端面到远端面构成的区域内的物体才能显示在影象上。

立即执行函数(function(){})(),先了解些函数的基本概念(函数声明、函数表达式、匿名函数)。加运算符确实可将函数声明转化为函数表达式,而之所以使用括号,是因为括号相对其他运算符会更安全。

在three.js基础上增加控制器

( function () {'use strict';var GizmoMaterial = function ( parameters ) {THREE.MeshBasicMaterial.call( this );this.depthTest = false;this.depthWrite = false;this.side = THREE.FrontSide;this.transparent = true;this.setValues( parameters );this.oldColor = this.color.clone();this.oldOpacity = this.opacity;this.highlight = function( highlighted ) {if ( highlighted ) {this.color.setRGB( 1, 1, 0 );this.opacity = 1;} else {this.color.copy( this.oldColor );this.opacity = this.oldOpacity;}};};GizmoMaterial.prototype = Object.create( THREE.MeshBasicMaterial.prototype );GizmoMaterial.prototype.constructor = GizmoMaterial;var GizmoLineMaterial = function ( parameters ) {THREE.LineBasicMaterial.call( this );this.depthTest = false;this.depthWrite = false;this.transparent = true;this.linewidth = 1;this.setValues( parameters );this.oldColor = this.color.clone();this.oldOpacity = this.opacity;this.highlight = function( highlighted ) {if ( highlighted ) {this.color.setRGB( 1, 1, 0 );this.opacity = 1;} else {this.color.copy( this.oldColor );this.opacity = this.oldOpacity;}};};GizmoLineMaterial.prototype = Object.create( THREE.LineBasicMaterial.prototype );GizmoLineMaterial.prototype.constructor = GizmoLineMaterial;var pickerMaterial = new GizmoMaterial( { visible: false, transparent: false } );THREE.TransformGizmo = function () {var scope = this;this.init = function () {THREE.Object3D.call( this );this.handles = new THREE.Object3D();this.pickers = new THREE.Object3D();this.planes = new THREE.Object3D();this.add( this.handles );this.add( this.pickers );this.add( this.planes );PLANESvar planeGeometry = new THREE.PlaneBufferGeometry( 50, 50, 2, 2 );var planeMaterial = new THREE.MeshBasicMaterial( { visible: false, side: THREE.DoubleSide } );var planes = {"XY":   new THREE.Mesh( planeGeometry, planeMaterial ),"YZ":   new THREE.Mesh( planeGeometry, planeMaterial ),"XZ":   new THREE.Mesh( planeGeometry, planeMaterial ),"XYZE": new THREE.Mesh( planeGeometry, planeMaterial )};this.activePlane = planes[ "XYZE" ];planes[ "YZ" ].rotation.set( 0, Math.PI / 2, 0 );planes[ "XZ" ].rotation.set( - Math.PI / 2, 0, 0 );for ( var i in planes ) {planes[ i ].name = i;this.planes.add( planes[ i ] );this.planes[ i ] = planes[ i ];}HANDLES AND PICKERSvar setupGizmos = function( gizmoMap, parent ) {for ( var name in gizmoMap ) {for ( i = gizmoMap[ name ].length; i --; ) {var object = gizmoMap[ name ][ i ][ 0 ];var position = gizmoMap[ name ][ i ][ 1 ];var rotation = gizmoMap[ name ][ i ][ 2 ];object.name = name;if ( position ) object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );if ( rotation ) object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );parent.add( object );}}};setupGizmos( this.handleGizmos, this.handles );setupGizmos( this.pickerGizmos, this.pickers );// reset Transformationsthis.traverse( function ( child ) {if ( child instanceof THREE.Mesh ) {child.updateMatrix();var tempGeometry = child.geometry.clone();tempGeometry.applyMatrix( child.matrix );child.geometry = tempGeometry;child.position.set( 0, 0, 0 );child.rotation.set( 0, 0, 0 );child.scale.set( 1, 1, 1 );}} );};this.highlight = function ( axis ) {this.traverse( function( child ) {if ( child.material && child.material.highlight ) {if ( child.name === axis ) {child.material.highlight( true );} else {child.material.highlight( false );}}} );};};THREE.TransformGizmo.prototype = Object.create( THREE.Object3D.prototype );THREE.TransformGizmo.prototype.constructor = THREE.TransformGizmo;THREE.TransformGizmo.prototype.update = function ( rotation, eye ) {var vec1 = new THREE.Vector3( 0, 0, 0 );var vec2 = new THREE.Vector3( 0, 1, 0 );var lookAtMatrix = new THREE.Matrix4();this.traverse( function( child ) {if ( child.name.search( "E" ) !== - 1 ) {child.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( eye, vec1, vec2 ) );} else if ( child.name.search( "X" ) !== - 1 || child.name.search( "Y" ) !== - 1 || child.name.search( "Z" ) !== - 1 ) {child.quaternion.setFromEuler( rotation );}} );};THREE.TransformGizmoTranslate = function () {THREE.TransformGizmo.call( this );var arrowGeometry = new THREE.Geometry();var mesh = new THREE.Mesh( new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false ) );mesh.position.y = 0.5;mesh.updateMatrix();arrowGeometry.merge( mesh.geometry, mesh.matrix );var lineXGeometry = new THREE.BufferGeometry();lineXGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  1, 0, 0 ], 3 ) );var lineYGeometry = new THREE.BufferGeometry();lineYGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  0, 1, 0 ], 3 ) );var lineZGeometry = new THREE.BufferGeometry();lineZGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  0, 0, 1 ], 3 ) );this.handleGizmos = {X: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ],[ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ]],Y: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ],[	new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]],Z: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ],[ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ]],XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ), [ 0, 0, 0 ], [ 0, 0, 0 ] ]],XY: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xffff00, opacity: 0.25 } ) ), [ 0.15, 0.15, 0 ] ]],YZ: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0x00ffff, opacity: 0.25 } ) ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ] ]],XZ: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.29, 0.29 ), new GizmoMaterial( { color: 0xff00ff, opacity: 0.25 } ) ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ] ]]};this.pickerGizmos = {X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ]],Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ]],Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]],XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), pickerMaterial ) ]],XY: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0.2, 0 ] ]],YZ: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ]],XZ: [[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), pickerMaterial ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ] ]]};this.setActivePlane = function ( axis, eye ) {var tempMatrix = new THREE.Matrix4();eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) );if ( axis === "X" ) {this.activePlane = this.planes[ "XY" ];if ( Math.abs( eye.y ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "XZ" ];}if ( axis === "Y" ) {this.activePlane = this.planes[ "XY" ];if ( Math.abs( eye.x ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "YZ" ];}if ( axis === "Z" ) {this.activePlane = this.planes[ "XZ" ];if ( Math.abs( eye.x ) > Math.abs( eye.y ) ) this.activePlane = this.planes[ "YZ" ];}if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ];if ( axis === "XY" ) this.activePlane = this.planes[ "XY" ];if ( axis === "YZ" ) this.activePlane = this.planes[ "YZ" ];if ( axis === "XZ" ) this.activePlane = this.planes[ "XZ" ];};this.init();};THREE.TransformGizmoTranslate.prototype = Object.create( THREE.TransformGizmo.prototype );THREE.TransformGizmoTranslate.prototype.constructor = THREE.TransformGizmoTranslate;THREE.TransformGizmoRotate = function () {THREE.TransformGizmo.call( this );var CircleGeometry = function ( radius, facing, arc ) {var geometry = new THREE.BufferGeometry();var vertices = [];arc = arc ? arc : 1;for ( var i = 0; i <= 64 * arc; ++ i ) {if ( facing === 'x' ) vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );if ( facing === 'y' ) vertices.push( Math.cos( i / 32 * Math.PI ) * radius, 0, Math.sin( i / 32 * Math.PI ) * radius );if ( facing === 'z' ) vertices.push( Math.sin( i / 32 * Math.PI ) * radius, Math.cos( i / 32 * Math.PI ) * radius, 0 );}geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );return geometry;};this.handleGizmos = {X: [[ new THREE.Line( new CircleGeometry( 1, 'x', 0.5 ), new GizmoLineMaterial( { color: 0xff0000 } ) ) ]],Y: [[ new THREE.Line( new CircleGeometry( 1, 'y', 0.5 ), new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]],Z: [[ new THREE.Line( new CircleGeometry( 1, 'z', 0.5 ), new GizmoLineMaterial( { color: 0x0000ff } ) ) ]],E: [[ new THREE.Line( new CircleGeometry( 1.25, 'z', 1 ), new GizmoLineMaterial( { color: 0xcccc00 } ) ) ]],XYZE: [[ new THREE.Line( new CircleGeometry( 1, 'z', 1 ), new GizmoLineMaterial( { color: 0x787878 } ) ) ]]};this.pickerGizmos = {X: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ] ]],Y: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ]],Z: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.12, 4, 12, Math.PI ), pickerMaterial ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ]],E: [[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.12, 2, 24 ), pickerMaterial ) ]],XYZE: [[ new THREE.Mesh( new THREE.Geometry() ) ]// TODO]};this.setActivePlane = function ( axis ) {if ( axis === "E" ) this.activePlane = this.planes[ "XYZE" ];if ( axis === "X" ) this.activePlane = this.planes[ "YZ" ];if ( axis === "Y" ) this.activePlane = this.planes[ "XZ" ];if ( axis === "Z" ) this.activePlane = this.planes[ "XY" ];};this.update = function ( rotation, eye2 ) {THREE.TransformGizmo.prototype.update.apply( this, arguments );var group = {handles: this[ "handles" ],pickers: this[ "pickers" ],};var tempMatrix = new THREE.Matrix4();var worldRotation = new THREE.Euler( 0, 0, 1 );var tempQuaternion = new THREE.Quaternion();var unitX = new THREE.Vector3( 1, 0, 0 );var unitY = new THREE.Vector3( 0, 1, 0 );var unitZ = new THREE.Vector3( 0, 0, 1 );var quaternionX = new THREE.Quaternion();var quaternionY = new THREE.Quaternion();var quaternionZ = new THREE.Quaternion();var eye = eye2.clone();worldRotation.copy( this.planes[ "XY" ].rotation );tempQuaternion.setFromEuler( worldRotation );tempMatrix.makeRotationFromQuaternion( tempQuaternion ).getInverse( tempMatrix );eye.applyMatrix4( tempMatrix );this.traverse( function( child ) {tempQuaternion.setFromEuler( worldRotation );if ( child.name === "X" ) {quaternionX.setFromAxisAngle( unitX, Math.atan2( - eye.y, eye.z ) );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );child.quaternion.copy( tempQuaternion );}if ( child.name === "Y" ) {quaternionY.setFromAxisAngle( unitY, Math.atan2( eye.x, eye.z ) );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );child.quaternion.copy( tempQuaternion );}if ( child.name === "Z" ) {quaternionZ.setFromAxisAngle( unitZ, Math.atan2( eye.y, eye.x ) );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );child.quaternion.copy( tempQuaternion );}} );};this.init();};THREE.TransformGizmoRotate.prototype = Object.create( THREE.TransformGizmo.prototype );THREE.TransformGizmoRotate.prototype.constructor = THREE.TransformGizmoRotate;THREE.TransformGizmoScale = function () {THREE.TransformGizmo.call( this );var arrowGeometry = new THREE.Geometry();var mesh = new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ) );mesh.position.y = 0.5;mesh.updateMatrix();arrowGeometry.merge( mesh.geometry, mesh.matrix );var lineXGeometry = new THREE.BufferGeometry();lineXGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  1, 0, 0 ], 3 ) );var lineYGeometry = new THREE.BufferGeometry();lineYGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  0, 1, 0 ], 3 ) );var lineZGeometry = new THREE.BufferGeometry();lineZGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,  0, 0, 1 ], 3 ) );this.handleGizmos = {X: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0xff0000 } ) ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ],[ new THREE.Line( lineXGeometry, new GizmoLineMaterial( { color: 0xff0000 } ) ) ]],Y: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x00ff00 } ) ), [ 0, 0.5, 0 ] ],[ new THREE.Line( lineYGeometry, new GizmoLineMaterial( { color: 0x00ff00 } ) ) ]],Z: [[ new THREE.Mesh( arrowGeometry, new GizmoMaterial( { color: 0x0000ff } ) ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ],[ new THREE.Line( lineZGeometry, new GizmoLineMaterial( { color: 0x0000ff } ) ) ]],XYZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), new GizmoMaterial( { color: 0xffffff, opacity: 0.25 } ) ) ]]};this.pickerGizmos = {X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ] ]],Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0.6, 0 ] ]],Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), pickerMaterial ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]],XYZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.4, 0.4, 0.4 ), pickerMaterial ) ]]};this.setActivePlane = function ( axis, eye ) {var tempMatrix = new THREE.Matrix4();eye.applyMatrix4( tempMatrix.getInverse( tempMatrix.extractRotation( this.planes[ "XY" ].matrixWorld ) ) );if ( axis === "X" ) {this.activePlane = this.planes[ "XY" ];if ( Math.abs( eye.y ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "XZ" ];}if ( axis === "Y" ) {this.activePlane = this.planes[ "XY" ];if ( Math.abs( eye.x ) > Math.abs( eye.z ) ) this.activePlane = this.planes[ "YZ" ];}if ( axis === "Z" ) {this.activePlane = this.planes[ "XZ" ];if ( Math.abs( eye.x ) > Math.abs( eye.y ) ) this.activePlane = this.planes[ "YZ" ];}if ( axis === "XYZ" ) this.activePlane = this.planes[ "XYZE" ];};this.init();};THREE.TransformGizmoScale.prototype = Object.create( THREE.TransformGizmo.prototype );THREE.TransformGizmoScale.prototype.constructor = THREE.TransformGizmoScale;THREE.TransformControls = function ( camera, domElement ) {// TODO: Make non-uniform scale and rotate play nice in hierarchies// TODO: ADD RXYZ contolTHREE.Object3D.call( this );domElement = ( domElement !== undefined ) ? domElement : document;this.object = undefined;this.visible = false;this.snap = null;this.space = "world";this.size = 1;this.axis = null;var scope = this;var _mode = "translate";var _dragging = false;var _plane = "XY";var _gizmo = {"translate": new THREE.TransformGizmoTranslate(),"rotate": new THREE.TransformGizmoRotate(),"scale": new THREE.TransformGizmoScale()};for ( var type in _gizmo ) {var gizmoObj = _gizmo[ type ];gizmoObj.visible = ( type === _mode );this.add( gizmoObj );}var changeEvent = { type: "change" };var mouseDownEvent = { type: "mouseDown" };var mouseUpEvent = { type: "mouseUp", mode: _mode };var objectChangeEvent = { type: "objectChange" };var ray = new THREE.Raycaster();var pointerVector = new THREE.Vector2();var point = new THREE.Vector3();var offset = new THREE.Vector3();var rotation = new THREE.Vector3();var offsetRotation = new THREE.Vector3();var scale = 1;var lookAtMatrix = new THREE.Matrix4();var eye = new THREE.Vector3();var tempMatrix = new THREE.Matrix4();var tempVector = new THREE.Vector3();var tempQuaternion = new THREE.Quaternion();var unitX = new THREE.Vector3( 1, 0, 0 );var unitY = new THREE.Vector3( 0, 1, 0 );var unitZ = new THREE.Vector3( 0, 0, 1 );var quaternionXYZ = new THREE.Quaternion();var quaternionX = new THREE.Quaternion();var quaternionY = new THREE.Quaternion();var quaternionZ = new THREE.Quaternion();var quaternionE = new THREE.Quaternion();var oldPosition = new THREE.Vector3();var oldScale = new THREE.Vector3();var oldRotationMatrix = new THREE.Matrix4();var parentRotationMatrix  = new THREE.Matrix4();var parentScale = new THREE.Vector3();var worldPosition = new THREE.Vector3();var worldRotation = new THREE.Euler();var worldRotationMatrix  = new THREE.Matrix4();var camPosition = new THREE.Vector3();var camRotation = new THREE.Euler();domElement.addEventListener( "mousedown", onPointerDown, false );domElement.addEventListener( "touchstart", onPointerDown, false );domElement.addEventListener( "mousemove", onPointerHover, false );domElement.addEventListener( "touchmove", onPointerHover, false );domElement.addEventListener( "mousemove", onPointerMove, false );domElement.addEventListener( "touchmove", onPointerMove, false );domElement.addEventListener( "mouseup", onPointerUp, false );domElement.addEventListener( "mouseout", onPointerUp, false );domElement.addEventListener( "touchend", onPointerUp, false );domElement.addEventListener( "touchcancel", onPointerUp, false );domElement.addEventListener( "touchleave", onPointerUp, false );this.dispose = function () {domElement.removeEventListener( "mousedown", onPointerDown );domElement.removeEventListener( "touchstart", onPointerDown );domElement.removeEventListener( "mousemove", onPointerHover );domElement.removeEventListener( "touchmove", onPointerHover );domElement.removeEventListener( "mousemove", onPointerMove );domElement.removeEventListener( "touchmove", onPointerMove );domElement.removeEventListener( "mouseup", onPointerUp );domElement.removeEventListener( "mouseout", onPointerUp );domElement.removeEventListener( "touchend", onPointerUp );domElement.removeEventListener( "touchcancel", onPointerUp );domElement.removeEventListener( "touchleave", onPointerUp );};this.attach = function ( object ) {this.object = object;this.visible = true;this.update();};this.detach = function () {this.object = undefined;this.visible = false;this.axis = null;};this.setMode = function ( mode ) {_mode = mode ? mode : _mode;if ( _mode === "scale" ) scope.space = "local";for ( var type in _gizmo ) _gizmo[ type ].visible = ( type === _mode );this.update();scope.dispatchEvent( changeEvent );};this.setSnap = function ( snap ) {scope.snap = snap;};this.setSize = function ( size ) {scope.size = size;this.update();scope.dispatchEvent( changeEvent );};this.setSpace = function ( space ) {scope.space = space;this.update();scope.dispatchEvent( changeEvent );};this.update = function () {if ( scope.object === undefined ) return;scope.object.updateMatrixWorld();worldPosition.setFromMatrixPosition( scope.object.matrixWorld );worldRotation.setFromRotationMatrix( tempMatrix.extractRotation( scope.object.matrixWorld ) );camera.updateMatrixWorld();camPosition.setFromMatrixPosition( camera.matrixWorld );camRotation.setFromRotationMatrix( tempMatrix.extractRotation( camera.matrixWorld ) );scale = worldPosition.distanceTo( camPosition ) / 6 * scope.size;this.position.copy( worldPosition );// NOTE: we are commenting this line because this transformation looks// confusing when using an ortographic camera// this.scale.set( scale, scale, scale );eye.copy( camPosition ).sub( worldPosition ).normalize();if ( scope.space === "local" ) {_gizmo[ _mode ].update( worldRotation, eye );} else if ( scope.space === "world" ) {_gizmo[ _mode ].update( new THREE.Euler(), eye );}_gizmo[ _mode ].highlight( scope.axis );};function onPointerHover( event ) {if ( scope.object === undefined || _dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return;var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;var intersect = intersectObjects( pointer, _gizmo[ _mode ].pickers.children );var axis = null;if ( intersect ) {axis = intersect.object.name;event.preventDefault();}if ( scope.axis !== axis ) {scope.axis = axis;scope.update();scope.dispatchEvent( changeEvent );}}function onPointerDown( event ) {if ( scope.object === undefined || _dragging === true || ( event.button !== undefined && event.button !== 0 ) ) return;var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;if ( pointer.button === 0 || pointer.button === undefined ) {var intersect = intersectObjects( pointer, _gizmo[ _mode ].pickers.children );if ( intersect ) {event.preventDefault();event.stopPropagation();scope.dispatchEvent( mouseDownEvent );scope.axis = intersect.object.name;scope.update();eye.copy( camPosition ).sub( worldPosition ).normalize();_gizmo[ _mode ].setActivePlane( scope.axis, eye );var planeIntersect = intersectObjects( pointer, [ _gizmo[ _mode ].activePlane ] );if ( planeIntersect ) {oldPosition.copy( scope.object.position );oldScale.copy( scope.object.scale );oldRotationMatrix.extractRotation( scope.object.matrix );worldRotationMatrix.extractRotation( scope.object.matrixWorld );parentRotationMatrix.extractRotation( scope.object.parent.matrixWorld );parentScale.setFromMatrixScale( tempMatrix.getInverse( scope.object.parent.matrixWorld ) );offset.copy( planeIntersect.point );}}}_dragging = true;}function onPointerMove( event ) {if ( scope.object === undefined || scope.axis === null || _dragging === false || ( event.button !== undefined && event.button !== 0 ) ) return;var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;var planeIntersect = intersectObjects( pointer, [ _gizmo[ _mode ].activePlane ] );if ( planeIntersect === false ) return;event.preventDefault();event.stopPropagation();point.copy( planeIntersect.point );if ( _mode === "translate" ) {point.sub( offset );point.multiply( parentScale );if ( scope.space === "local" ) {point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );if ( scope.axis.search( "X" ) === - 1 ) point.x = 0;if ( scope.axis.search( "Y" ) === - 1 ) point.y = 0;if ( scope.axis.search( "Z" ) === - 1 ) point.z = 0;point.applyMatrix4( oldRotationMatrix );scope.object.position.copy( oldPosition );scope.object.position.add( point );}if ( scope.space === "world" || scope.axis.search( "XYZ" ) !== - 1 ) {if ( scope.axis.search( "X" ) === - 1 ) point.x = 0;if ( scope.axis.search( "Y" ) === - 1 ) point.y = 0;if ( scope.axis.search( "Z" ) === - 1 ) point.z = 0;point.applyMatrix4( tempMatrix.getInverse( parentRotationMatrix ) );scope.object.position.copy( oldPosition );scope.object.position.add( point );}if ( scope.snap !== null ) {if ( scope.axis.search( "X" ) !== - 1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap;if ( scope.axis.search( "Y" ) !== - 1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap;if ( scope.axis.search( "Z" ) !== - 1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap;}} else if ( _mode === "scale" ) {point.sub( offset );point.multiply( parentScale );if ( scope.space === "local" ) {if ( scope.axis === "XYZ" ) {scale = 1 + ( ( point.y ) / 50 );scope.object.scale.x = oldScale.x * scale;scope.object.scale.y = oldScale.y * scale;scope.object.scale.z = oldScale.z * scale;} else {point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );if ( scope.axis === "X" ) scope.object.scale.x = oldScale.x * ( 1 + point.x / 50 );if ( scope.axis === "Y" ) scope.object.scale.y = oldScale.y * ( 1 + point.y / 50 );if ( scope.axis === "Z" ) scope.object.scale.z = oldScale.z * ( 1 + point.z / 50 );}}} else if ( _mode === "rotate" ) {point.sub( worldPosition );point.multiply( parentScale );tempVector.copy( offset ).sub( worldPosition );tempVector.multiply( parentScale );if ( scope.axis === "E" ) {point.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) );tempVector.applyMatrix4( tempMatrix.getInverse( lookAtMatrix ) );rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) );offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) );tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) );quaternionE.setFromAxisAngle( eye, rotation.z - offsetRotation.z );quaternionXYZ.setFromRotationMatrix( worldRotationMatrix );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionE );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ );scope.object.quaternion.copy( tempQuaternion );} else if ( scope.axis === "XYZE" ) {quaternionE.setFromEuler( point.clone().cross( tempVector ).normalize() ); // rotation axistempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) );quaternionX.setFromAxisAngle( quaternionE, - point.clone().angleTo( tempVector ) );quaternionXYZ.setFromRotationMatrix( worldRotationMatrix );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ );scope.object.quaternion.copy( tempQuaternion );} else if ( scope.space === "local" ) {point.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );tempVector.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) );offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) );quaternionXYZ.setFromRotationMatrix( oldRotationMatrix );quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );if ( scope.axis === "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX );if ( scope.axis === "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY );if ( scope.axis === "Z" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionZ );scope.object.quaternion.copy( quaternionXYZ );} else if ( scope.space === "world" ) {rotation.set( Math.atan2( point.z, point.y ), Math.atan2( point.x, point.z ), Math.atan2( point.y, point.x ) );offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) );tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) );quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );quaternionXYZ.setFromRotationMatrix( worldRotationMatrix );if ( scope.axis === "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );if ( scope.axis === "Y" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionY );if ( scope.axis === "Z" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionZ );tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionXYZ );scope.object.quaternion.copy( tempQuaternion );}}scope.update();scope.dispatchEvent( changeEvent );scope.dispatchEvent( objectChangeEvent );}function onPointerUp( event ) {if ( event.button !== undefined && event.button !== 0 ) return;if ( _dragging && ( scope.axis !== null ) ) {mouseUpEvent.mode = _mode;scope.dispatchEvent( mouseUpEvent )}_dragging = false;onPointerHover( event );}function intersectObjects( pointer, objects ) {var rect = domElement.getBoundingClientRect();var x = ( pointer.clientX - rect.left ) / rect.width;var y = ( pointer.clientY - rect.top ) / rect.height;pointerVector.set( ( x * 2 ) - 1, - ( y * 2 ) + 1 );ray.setFromCamera( pointerVector, camera );var intersections = ray.intersectObjects( objects, true );return intersections[ 0 ] ? intersections[ 0 ] : false;}};THREE.TransformControls.prototype = Object.create( THREE.Object3D.prototype );THREE.TransformControls.prototype.constructor = THREE.TransformControls;THREE.OrbitControls = function ( object, domElement ) {this.object = object;this.domElement = ( domElement !== undefined ) ? domElement : document;// APIthis.enabled = true;this.center = new THREE.Vector3();this.userZoom = true;this.userZoomSpeed = 1.0;this.userRotate = true;this.userRotateSpeed = 1.0;this.userPan = true;this.userPanSpeed = 2.0;this.autoRotate = false;this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60this.minPolarAngle = 0; // radiansthis.maxPolarAngle = Math.PI; // radiansthis.minDistance = 0;this.maxDistance = Infinity;// 65 /*A*/, 83 /*S*/, 68 /*D*/this.keys = { LEFT: 37, UP: 38, RIGHT: 39, BOTTOM: 40, ROTATE: 65, ZOOM: 83, PAN: 68 };// internalsvar scope = this;var EPS = 0.000001;var PIXELS_PER_ROUND = 1800;var rotateStart = new THREE.Vector2();var rotateEnd = new THREE.Vector2();var rotateDelta = new THREE.Vector2();var zoomStart = new THREE.Vector2();var zoomEnd = new THREE.Vector2();var zoomDelta = new THREE.Vector2();var phiDelta = 0;var thetaDelta = 0;var scale = 1;var lastPosition = new THREE.Vector3();var STATE = { NONE: -1, ROTATE: 0, ZOOM: 1, PAN: 2 };var state = STATE.NONE;// eventsvar changeEvent = { type: 'change' };this.rotateLeft = function ( angle ) {if ( angle === undefined ) {angle = getAutoRotationAngle();}thetaDelta -= angle;};this.rotateRight = function ( angle ) {if ( angle === undefined ) {angle = getAutoRotationAngle();}thetaDelta += angle;};this.rotateUp = function ( angle ) {if ( angle === undefined ) {angle = getAutoRotationAngle();}phiDelta -= angle;};this.rotateDown = function ( angle ) {if ( angle === undefined ) {angle = getAutoRotationAngle();}phiDelta += angle;};this.zoomIn = function ( zoomScale ) {if ( zoomScale === undefined ) {zoomScale = getZoomScale();}scale /= zoomScale;};this.zoomOut = function ( zoomScale ) {if ( zoomScale === undefined ) {zoomScale = getZoomScale();}scale *= zoomScale;};this.pan = function ( distance ) {distance.transformDirection( this.object.matrix );distance.multiplyScalar( scope.userPanSpeed );this.object.position.add( distance );this.center.add( distance );};this.update = function () {var position = this.object.position;var offset = position.clone().sub( this.center );// angle from z-axis around y-axisvar theta = Math.atan2( offset.x, offset.z );// angle from y-axisvar phi = Math.atan2( Math.sqrt( offset.x * offset.x + offset.z * offset.z ), offset.y );if ( this.autoRotate ) {this.rotateLeft( getAutoRotationAngle() );}theta += thetaDelta;phi += phiDelta;// restrict phi to be between desired limitsphi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, phi ) );// restrict phi to be betwee EPS and PI-EPSphi = Math.max( EPS, Math.min( Math.PI - EPS, phi ) );var radius = offset.length() * scale;// restrict radius to be between desired limitsradius = Math.max( this.minDistance, Math.min( this.maxDistance, radius ) );offset.x = radius * Math.sin( phi ) * Math.sin( theta );offset.y = radius * Math.cos( phi );offset.z = radius * Math.sin( phi ) * Math.cos( theta );position.copy( this.center ).add( offset );this.object.lookAt( this.center );thetaDelta = 0;phiDelta = 0;scale = 1;if ( lastPosition.distanceTo( this.object.position ) > 0 ) {this.dispatchEvent( changeEvent );lastPosition.copy( this.object.position );}};function getAutoRotationAngle() {return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;}function getZoomScale() {return Math.pow( 0.95, scope.userZoomSpeed );}function onMouseDown( event ) {if ( scope.enabled === false ) return;if ( scope.userRotate === false ) return;event.preventDefault();if ( state === STATE.NONE ){if ( event.button === 0 )state = STATE.ROTATE;if ( event.button === 1 )state = STATE.ZOOM;if ( event.button === 2 )state = STATE.PAN;}if ( state === STATE.ROTATE ) {//state = STATE.ROTATE;rotateStart.set( event.clientX, event.clientY );} else if ( state === STATE.ZOOM ) {//state = STATE.ZOOM;zoomStart.set( event.clientX, event.clientY );} else if ( state === STATE.PAN ) {//state = STATE.PAN;}document.addEventListener( 'mousemove', onMouseMove, false );document.addEventListener( 'mouseup', onMouseUp, false );}function onMouseMove( event ) {if ( scope.enabled === false ) return;event.preventDefault();if ( state === STATE.ROTATE ) {rotateEnd.set( event.clientX, event.clientY );rotateDelta.subVectors( rotateEnd, rotateStart );scope.rotateLeft( 2 * Math.PI * rotateDelta.x / PIXELS_PER_ROUND * scope.userRotateSpeed );scope.rotateUp( 2 * Math.PI * rotateDelta.y / PIXELS_PER_ROUND * scope.userRotateSpeed );rotateStart.copy( rotateEnd );} else if ( state === STATE.ZOOM ) {zoomEnd.set( event.clientX, event.clientY );zoomDelta.subVectors( zoomEnd, zoomStart );if ( zoomDelta.y > 0 ) {scope.zoomIn();} else {scope.zoomOut();}zoomStart.copy( zoomEnd );} else if ( state === STATE.PAN ) {var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;scope.pan( new THREE.Vector3( - movementX, movementY, 0 ) );}}function onMouseUp( event ) {if ( scope.enabled === false ) return;if ( scope.userRotate === false ) return;document.removeEventListener( 'mousemove', onMouseMove, false );document.removeEventListener( 'mouseup', onMouseUp, false );state = STATE.NONE;}function onMouseWheel( event ) {if ( scope.enabled === false ) return;if ( scope.userZoom === false ) return;var delta = 0;if ( event.wheelDelta ) { // WebKit / Opera / Explorer 9delta = event.wheelDelta;} else if ( event.detail ) { // Firefoxdelta = - event.detail;}if ( delta > 0 ) {scope.zoomOut();} else {scope.zoomIn();}}function onKeyDown( event ) {if ( scope.enabled === false ) return;if ( scope.userPan === false ) return;switch ( event.keyCode ) {/*case scope.keys.UP:scope.pan( new THREE.Vector3( 0, 1, 0 ) );break;case scope.keys.BOTTOM:scope.pan( new THREE.Vector3( 0, - 1, 0 ) );break;case scope.keys.LEFT:scope.pan( new THREE.Vector3( - 1, 0, 0 ) );break;case scope.keys.RIGHT:scope.pan( new THREE.Vector3( 1, 0, 0 ) );break;*/case scope.keys.ROTATE:state = STATE.ROTATE;break;case scope.keys.ZOOM:state = STATE.ZOOM;break;case scope.keys.PAN:state = STATE.PAN;break;}}function onKeyUp( event ) {switch ( event.keyCode ) {case scope.keys.ROTATE:case scope.keys.ZOOM:case scope.keys.PAN:state = STATE.NONE;break;}}this.domElement.addEventListener( 'contextmenu', function ( event ) { event.preventDefault(); }, false );this.domElement.addEventListener( 'mousedown', onMouseDown, false );this.domElement.addEventListener( 'mousewheel', onMouseWheel, false );this.domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefoxwindow.addEventListener( 'keydown', onKeyDown, false );window.addEventListener( 'keyup', onKeyUp, false );};THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );}() );

初始化三维场景,并增加点云、三个模型和控制功能

//
// Utils 工具
//var Utils =  {};Utils.createShadow = function (mesh, material) {var params = mesh.geometry.parameters;mesh.geometry.computeBoundingSphere();var geo = mesh.geometry.type === 'BoxGeometry'? new THREE.PlaneGeometry(params.width, params.depth): new THREE.CircleGeometry(mesh.geometry.boundingSphere.radius, 24);var shadow = new THREE.Mesh(geo, material);shadow.rotation.x = -Math.PI / 2;shadow.position.x = mesh.position.x;shadow.position.z = mesh.position.z;return shadow;
};Utils.updateShadow = function (shadow, target) {shadow.position.x = target.position.x;shadow.position.z = target.position.z;shadow.visible = target.position.y >= 0;shadow.scale.x = target.scale.x;shadow.scale.y = target.scale.z;
};function init(){var WIDTH = 800;var HEIGHT = 600;this._previousElapsed = 0;// setup a WebGL renderer within an existing canvasvar canvas = document.getElementById('demo');// Renderer渲染器设置this.renderer = new THREE.WebGLRenderer({//抗锯齿属性,WebGLRenderer常用的一个属性antialias:true,alpha:true,canvas: canvas});this.renderer.setClearColor(0xb9d3ff, 0.2); //设置背景颜色和透明度canvas.width = WIDTH;canvas.height = HEIGHT;this.renderer.setViewport(0, 0, WIDTH, HEIGHT);// create the scene Scene设置this.scene = new THREE.Scene();this.scene.background = new THREE.Color('#e6e6e6');const fov = 40 // 视野范围const aspect = 2 // 相机默认值 画布的宽高比const near = 0.1 // 近平面const far = 1000 // 远平面// 透视投影相机this.camera = new THREE.PerspectiveCamera(fov, aspect, near, far)// create an isometric camera 正交相机//this.camera = new THREE.OrthographicCamera(-5, 5, 5, -5, -1, 100);this.camera.position.set(10, 10, 10)this.camera.lookAt(this.scene.position); // point at origin//this.camera.lookAt(0, 0, 0)// create ground and axis / grid helpersvar ground = new THREE.Mesh(new THREE.PlaneGeometry(10, 10),new THREE.MeshBasicMaterial({color: 0xcccccc}));ground.rotation.x = -Math.PI / 2;ground.position.y = -0.01; // to avoid z-fighting with axis and shadowsthis.scene.add(ground);this.scene.add((new THREE.AxesHelper(8)));// 光源const lightColor = 0xffffff;const intensity = 1;const light = new THREE.DirectionalLight(lightColor, intensity);this.scene.add(light);// 材质this.materials = {shadow: new THREE.MeshBasicMaterial({color: 0x000000,transparent: true,opacity: 0.5}),solid: new THREE.MeshNormalMaterial({}),colliding: new THREE.MeshBasicMaterial({color: 0xff0000,transparent: true,opacity: 0.5}),dot: new THREE.MeshBasicMaterial({color: 0x0000ff})};this.knot = new THREE.Mesh(new THREE.TorusKnotGeometry(0.5, 0.1), this.materials.solid);this.knot.position.set(-3, 2, 1);this.knot.geometry.computeBoundingSphere();this.sphere = new THREE.Mesh(new THREE.SphereGeometry(1), this.materials.solid);this.sphere.position.set(2, 2, 0);this.sphereShadow = Utils.createShadow(this.sphere, this.materials.shadow);// the object the user can control to check for collisionsthis.cube = new THREE.Mesh(new THREE.BoxGeometry(0.75, 0.75, 0.75),this.materials.solid);this.cube.position.set(2, 2, 1.74);this.cubeShadow = Utils.createShadow(this.cube, this.materials.shadow);// add objects to the scenethis.scene.add(this.cube);this.scene.add(this.knot);this.scene.add(this.sphere);// add fake shadows to the scenethis.scene.add(Utils.createShadow(this.knot, this.materials.shadow));this.scene.add(this.sphereShadow);this.scene.add(this.cubeShadow);// 添加相机控制器this.controls = new THREE.OrbitControls( this.camera, this.renderer.domElement );// 开启控制器的阻尼效果this.controls.enableDamping = truethis.scene.add(this.controls);this.transFormControls = new THREE.TransformControls(this.camera, this.renderer.domElement);this.transFormControls.space = 'world';this.transFormControls.attach(this.cube);this.scene.add(this.transFormControls);this.timestamp = 0;function render(elapsed){window.requestAnimationFrame(render);// compute delta time in seconds -- also cap itvar delta = (elapsed - this._previousElapsed) / 1000.0;delta = Math.min(delta, 0.25); // maximum delta of 250 msthis._previousElapsed = elapsed;this.update(delta);this.renderer.render(this.scene, this.camera);}this.world = new CANNON.World();this.addPhysicalBody = function (mesh, bodyOptions) {var shape;// create a Sphere shape for spheres and thorus knots,// a Box shape otherwiseif (mesh.geometry.type === 'SphereGeometry' || mesh.geometry.type === 'ThorusKnotGeometry') {mesh.geometry.computeBoundingSphere();shape = new CANNON.Sphere(mesh.geometry.boundingSphere.radius);}else {mesh.geometry.computeBoundingBox();var box = mesh.geometry.boundingBox;shape = new CANNON.Box(new CANNON.Vec3((box.max.x - box.min.x) / 2,(box.max.y - box.min.y) / 2,(box.max.z - box.min.z) / 2));}var body = new CANNON.Body(bodyOptions);body.addShape(shape);body.position.copy(mesh.position);body.computeAABB();// disable collision response so objects don't move when they collide// against each otherbody.collisionResponse = false;// keep a reference to the mesh so we can update its properties laterbody.mesh = mesh;this.world.addBody(body);return body;};this.initPhysicalWorld = function() {// add physical bodiesthis.knotBody = this.addPhysicalBody(this.knot, {mass: 1});this.addPhysicalBody(this.sphere, {mass: 1});this.cubeBody = this.addPhysicalBody(this.cube, {mass: 1});// register for collide eventsthis.cubeBody.addEventListener('collide', function (e) {console.log('Collision!');}.bind(this));// rotate the knotthis.knotBody.angularVelocity.x = Math.PI / 4;};this.update = function (delta) {this.timestamp += delta;// move the cube body with mouse controlsthis.controls.update();this.transFormControls.update();this.cubeBody.position.copy(this.cube.position);// update the cube shadowUtils.updateShadow(this.cubeShadow, this.cube);// update knot mesh rotationthis.knot.quaternion.copy(this.knotBody.quaternion);// reset materialsthis.sphere.material = this.materials.solid;this.knot.material = this.materials.solid;this.updatePhysics(delta);};this.updatePhysics = function (delta) {this.world.step(delta);this.world.contacts.forEach(function (contact) {contact.bi.mesh.material = this.materials.colliding;contact.bj.mesh.material = this.materials.colliding;this.cube.material = this.materials.solid;}.bind(this));};// setup physic worldthis.initPhysicalWorld();this.addPointCloud = function(){// 彩色点云var particles = 50 * 1000;var geometry = new THREE.BufferGeometry();// 生成 5万个点需要的存储空间var positions = new Float32Array(particles * 3);// 每个顶点一种颜色var colors = new Float32Array(particles * 3);var color = new THREE.Color();var n = 4, n2 = n / 2; // 限定点出现的范围是[-2,2]这么一个立方体中,n2表示直径的一半for (var i = 0; i < positions.length; i += 9) {// 通过随机数生成点的位置// 生成一个顶点,范围是[-2,2]var x = Math.random() * n - n2;var y = Math.random() * n - n2;var z = Math.random() * n - n2;// 随机生成点positions[i] = x;positions[i + 1] = y;positions[i + 2] = z;// 为每个顶点赋值颜色// x / n得到范围[-0.5,0.5],加0.5得到[0,1]范围的颜色var vx = (x / n) + 0.5var vy = (y / n) + 0.5var vz = (z / n) + 0.5color.setRGB(vx, vy, vz)colors[i] = color.rcolors[i + 1] = color.gcolors[i + 2] = color.b}geometry.addAttribute('position', new THREE.BufferAttribute(positions, 3))geometry.addAttribute('color', new THREE.BufferAttribute(colors, 3))// 计算几何体的包围盒geometry.computeBoundingSphere();// 创建点材质var material = new THREE.PointsMaterial({size: 0.1,vertexColors: THREE.VertexColors})// 创建点particleSystem = new THREE.Points(geometry, material);this.scene.add(particleSystem);}this.addPointCloud();window.requestAnimationFrame(render);}//
// main
//window.onload = function () {try {init();} catch (error) {console.error(error);// Expected output: ReferenceError: nonExistentFunction is not defined// (Note: the exact output may be browser-dependent)}
};

实现Web 3D效果

移动盒子,通过物理引擎cannon.js实现碰撞检测。

    this.world = new CANNON.World();this.addPhysicalBody = function (mesh, bodyOptions) {var shape;// create a Sphere shape for spheres and thorus knots,// a Box shape otherwiseif (mesh.geometry.type === 'SphereGeometry' || mesh.geometry.type === 'ThorusKnotGeometry') {mesh.geometry.computeBoundingSphere();shape = new CANNON.Sphere(mesh.geometry.boundingSphere.radius);}else {mesh.geometry.computeBoundingBox();var box = mesh.geometry.boundingBox;shape = new CANNON.Box(new CANNON.Vec3((box.max.x - box.min.x) / 2,(box.max.y - box.min.y) / 2,(box.max.z - box.min.z) / 2));}var body = new CANNON.Body(bodyOptions);body.addShape(shape);body.position.copy(mesh.position);body.computeAABB();// disable collision response so objects don't move when they collide// against each otherbody.collisionResponse = false;// keep a reference to the mesh so we can update its properties laterbody.mesh = mesh;this.world.addBody(body);return body;};this.initPhysicalWorld = function() {// add physical bodiesthis.knotBody = this.addPhysicalBody(this.knot, {mass: 1});this.addPhysicalBody(this.sphere, {mass: 1});this.cubeBody = this.addPhysicalBody(this.cube, {mass: 1});// register for collide eventsthis.cubeBody.addEventListener('collide', function (e) {console.log('Collision!');}.bind(this));// rotate the knotthis.knotBody.angularVelocity.x = Math.PI / 4;};

HTML结构

<!DOCTYPE html>
<html>
<head><title>three.js+cannon.js Web 3D</title><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1"><meta name="renderer" content="webkit"><meta name="force-rendering" content="webkit"><meta http-equiv="X-UA-Compatible" content="IE=10,chrome=1"><meta data-rh="true" name="keywords" content="three.js,JavaScript,canon.js"><meta data-rh="true" name="description" content="three.js+cannon.js Web 3D"><meta data-rh="true" property="og:title" content="THREE.JS and CANON.JS"><meta data-rh="true" property="og:url" content=""><meta data-rh="true" property="og:description" content="three.js+cannon.js Web 3D"><meta data-rh="true" property="og:image" content=""><meta data-rh="true" property="og:type" content="article"><meta data-rh="true" property="og:site_name" content=""><link rel="stylesheet" href="styles.css" type="text/css"><script src="https://cdn.bootcdn.net/ajax/libs/three.js/109/three.min.js"></script><script src="https://cdn.bootcdn.net/ajax/libs/cannon.js/0.6.2/cannon.min.js"></script><script src="js/lib/TransformControls.js"></script><script src="js/index.js"></script></head><body><header class="main-header"><h1><a href="index.html">Web 3D</a></h1><p>Using cannon.js (physics engine)</p>
</header><!-- main wrapper -->
<div class="main-wrapper"><!-- canvas wrapper --><div class="canvas-wrapper"><canvas id="demo"></canvas><aside class="message"><strong>Drag</strong> the cube around</aside></div><!-- canvas wrapper --><footer class="main-footer"><p>Source code</p></footer>
</div>
<!-- main wrapper --></body>
</html>

CSS样式

body {font-family: "Helvetica Neue", Helvetica, Arial, "PingFang SC", "Hiragino Sans GB", "Heiti SC", "Microsoft YaHei", "WenQuanYi Micro Hei", sans-serif;margin: 0;
}canvas {background: transparent;padding: 0;margin: 0;border: 5px solid #31ffd5;
}a, a:hover, a:visited {color: #000;text-decoration: none;
}a {border-bottom: 2px solid #31ffd5;
}a:hover {background:#31ffd5;
}.main-wrapper {margin: 0 auto;text-align: center;
}.main-header, .main-footer {text-align: center;
}.subtitle {font-size: 1.5em;
}.main-header {margin-top: 0;margin-bottom: 2em;transform: perspective(750px) translate3d(0px, 0px, -250px) rotateX(27deg) scale(0.96, 0.96);border-radius: 20px;border: 5px solid #e6e6e6;box-shadow: 0 70px 40px -20px rgba(0, 0, 0, 0.2);transition: 0.4s ease-in-out transform;
}.main-header h1 {margin: 0;background: #31ffd5;padding: 0.5em 1em;
}.main-header a {color: #000;text-decoration: none;
}.main-footer {margin-top: 2em;
}.canvas-wrapper {position: relative;background: #12345611;
}.message {position: absolute;color: #fff;bottom: 0;left: 0;right: 0;padding: 5px;text-align: center;
}.message strong {color: #31ffd5;
}li, p {line-height: 1.25em;
}li {margin: 0.5em 0;
}

cannon.js

cannon.js是一个轻量级且简单的网络三维物理引擎。受three.js和ammon.js的启发,加上网络缺乏物理引擎,cannon.js应运而生。刚体物理引擎包括简单的碰撞检测、各种体型、接触、摩擦和约束。文件大小比许多移植的物理引擎更小。100%开源JavaScript,使用迭代高斯-塞德尔Gauss-Seidel解算器来求解约束。使用SPOOK步进器。

https://schteppe.github.io/cannon.js/

Three.js 官方文档

Three.js 官方文档是学习和使用 Three.js 这一基于 WebGL 的 JavaScript 3D 图形库的重要资源。它提供了丰富的信息,包括各种功能方法属性的详细描述,以及许多示例和教程。

https://threejs.org/docs/index.html

Three.js 中文网

Three.js 中文网是一个专注于 Three.js 资源分享与交流的中文社区网站。该网站提供了大量与 Three.js 相关的教程案例插件工具,帮助开发者更好地学习和应用 Three.js。

http://webgl3d.cn/

Three.js简版案例

Three.js简版案例集合的目标是提供一组基本的、有指导意义的示例,介绍Three.js中的各种功能。每个页面的源代码都包含详细的注释。

https://stemkoski.github.io/Three.js/

探索three.js

探索three.js是对 web 作为 3D 图形平台的完整介绍,它使用 three.js WebGL 库,编写自一位核心 three.js 开发人员。可以作为一个引导读者深入了解 three.js 库的指南。

https://discoverthreejs.com/zh/

three.js editor

three.js editor是一个基于 Three.js 库的在线3D编辑器。这个编辑器允许用户直接在网页上创建和编辑 3D 场景。在 Three.js 编辑器中,你可以:

  • 创建和编辑 3D 对象:通过编辑器提供的工具,你可以创建各种几何体(如立方体、球体、圆柱体等),并调整它们的位置、旋转和缩放。

  • 添加材质和贴图:为 3D 对象添加不同的材质,如基本材质、物理材质等,并可以应用贴图来改变对象的外观。

  • 设置光源和环境:在场景中添加不同类型的光源(如点光源、平行光源、环境光等),并调整光照参数。

  • 实时预览:编辑器会实时渲染你的 3D 场景,让你可以立即看到所做的更改。

  • 导入和导出模型:支持导入和导出多种 3D 文件格式,如 glTF、OBJ 等。

https://threejs.org/editor/

Shadertoy 

Shadertoy是一个基于 WebGL 的在线实时渲染平台,主要用于编辑分享查看着色器shader 程序及其实现的效果。

在这个平台上,用户可以创作和分享自己的 3D 图形效果。它提供了一个简单方便的环境,让用户可以轻松编辑自己的片段着色器,并实时查看修改的效果。

同时,Shadertoy 上有许多大佬分享他们制作的酷炫效果的代码,这些代码是完全开源的,用户可以在这些代码的基础上进行修改和学习。

除此之外,Shadertoy 还允许用户选择声音频道,将当前帧的声音信息转变成纹理(Texture),传入 shader 当中,从而根据声音信息来控制图形。这使得 Shadertoy 在视觉和听觉的结合上有了更多的可能性。

https://www.shadertoy.com/

glsl.app 

glsl.app 是一个在线的 GLSL (OpenGL Shading Language) 编辑器。GLSL 是一种用于图形渲染的着色语言,特别是在 OpenGL 图形库中。这种语言允许开发者为图形硬件编写着色器程序,这些程序可以运行在 GPU 上,用于计算图像的各种视觉效果。在 glsl.app 上,你可以:

  • 编写和编辑着色器代码:直接在网页上编写顶点着色器、片元着色器等。

  • 实时预览:当你编写或修改着色器代码时,可以立即在右侧的预览窗口中看到效果。

  • 分享你的作品:完成你的着色器后,你可以获得一个链接,通过这个链接与其他人分享你的作品。

  • 学习:如果你是初学者,该网站还提供了很多示例和教程,帮助你了解如何编写各种着色器效果。

Online WebGL (GLSL) Shaders Editor and Playground

参见:

解释基本的 3D 原理 - 游戏开发 | MDN

Three.js - examples

Three.js教程

《探索three.js》

three.js docs

3D 碰撞检测 - 游戏开发 | MDN

Mozilla Developer Relations · GitHub

Cannon.js - JavaScripting

schteppe/cannon.js @ GitHub

Beautiful CSS 3D Transform Perspective Examples in 2024 | Polypane

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/707134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

jmeter(四)HTTP请求

启动jmeter&#xff0c;建立一个测试计划 这里再次说说怎么安装和启动jmeter吧&#xff0c;昨天下午又被人问到怎样安装和使用&#xff0c;我也是醉了&#xff1b;在我看来&#xff0c;百度能解决百分之八十的问题&#xff0c;特别是基础的问题。。。 安装&#xff1a;去官网…

账户名密码是怎样被窃取的,简单模拟攻击者权限维持流程。

前言 在我们进行渗透测试的时候&#xff0c;常常需要进行权限维持&#xff0c;常见的 Javascript窃取用户凭证是一种常见的攻击手法。之前我们可能学习过钓鱼网页的使用&#xff0c;如果我们通过渗透测试进入到用户的服务器&#xff0c;其实也可以通过在网页中植入Javascript代…

Python + Selenium —— 常用控制方法!

Selenium 体系中用来操作浏览器的 API 就是 WebDriver&#xff0c;WebDriver 针对多种语言都实现了一套 API&#xff0c;支持多种编程语言。 Selenium 通常用来做自动化测试&#xff0c;或者编写网络爬虫。 通常我们说的 Selenium 自动化操作&#xff0c;指的就是 WebDriver …

AI:138-开发一种能够自动化生成艺术品描述的人工智能系统

🚀点击这里跳转到本专栏,可查阅专栏顶置最新的指南宝典~ 🎉🎊🎉 你的技术旅程将在这里启航! 从基础到实践,深入学习。无论你是初学者还是经验丰富的老手,对于本专栏案例和项目实践都有参考学习意义。 ✨✨✨ 每一个案例都附带关键代码,详细讲解供大家学习,希望…

备战蓝桥杯————如何判断回文链表

如何判断回文链表 题目描述 给你一个单链表的头节点 head &#xff0c;请你判断该链表是否为回文链表。如果是&#xff0c;返回 true &#xff1b;否则&#xff0c;返回 false 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,2,1] 输出&#xff1a;true示例 2&#xff1a;…

【MySQL】SQL 入门和 DDL

1. 通用语法 SQL语句可以单行或多行书写&#xff0c;以分号结尾SQL语句可以使用空格/缩进来增强语句的可读性MySQL数据库的SQL语句不区分大小写&#xff0c;关键字建议使用大写注释&#xff1a; 单行注释&#xff1a;-- 注释内容 或 # 注释内容多行注释&#xff1a;/* 注释内容…

Linux线程(二)----- 线程控制

目录 前言 一、线程资源区 1.1 线程私有资源 1.2 线程共享资源 1.3 原生线程库 二、线程控制接口 2.1 线程创建 2.1.1 创建一批线程 2.2 线程等待 2.3 终止线程 2.4 线程实战 2.5 其他接口 2.5.1 关闭线程 2.5.2 获取线程ID 2.5.3 线程分离 三、深入理解线程 …

挑战杯 基于YOLO实现的口罩佩戴检测 - python opemcv 深度学习

文章目录 0 前言1 课题介绍2 算法原理2.1 算法简介2.2 网络架构 3 关键代码4 数据集4.1 安装4.2 打开4.3 选择yolo标注格式4.4 打标签4.5 保存 5 训练6 实现效果6.1 pyqt实现简单GUI6.3 视频识别效果6.4 摄像头实时识别 7 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xf…

2024 值得推荐的免费开源 WAF

WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击。 开源 WAF 和商用 WAF&#xff08;奇安信…

idea生成WebServices接口

文章目录 idea生成WebServices接口1.创建接口2.生成wsdl文件3.在soapUI中&#xff0c;生成6个文件4.将生成的文件拷贝到工程中5.在service-config中注册服务 idea生成WebServices接口 1.创建接口 新建一个webServices工程&#xff0c;按照接口规范生成接口、请求类、响应类。…

LVS负载均衡服务器

简介: LVS (Linux Virtual Server):四层路由设备&#xff0c;是由中国人章文松研发的(阿里巴巴的副总裁)根据用户请求的IP与端口号实现将用户的请求分发至不同的主机。 工作原理: LVS工作在一台server上提供Directory(负载均衡器)的功能&#xff0c;本身并不提供服务&#xff…

认识AJAX

一、什么是Ajax? 有跳转就是同步&#xff0c;无跳转就是异步 Asynchronous Javascript And XML&#xff08;异步JavaScript和XML&#xff09; Ajax 异步 JavaScript 和XML。Ajax是一种用于创建快速动态网页的技术通过在后台与服务器进行少量数据交换&#xff0c;Ajax可以使网…

Swagger接口文档管理工具

Swagger 1、Swagger1.1 swagger介绍1.2 项目集成swagger流程1.3 项目集成swagger 2、knife4j2.1 knife4j介绍2.2 项目集成knife4j 1、Swagger 1.1 swagger介绍 官网&#xff1a;https://swagger.io/ Swagger 是一个规范和完整的Web API框架&#xff0c;用于生成、描述、调用和…

stm32——hal库学习笔记(ADC)

这里写目录标题 一、ADC简介&#xff08;了解&#xff09;1.1&#xff0c;什么是ADC&#xff1f;1.2&#xff0c;常见的ADC类型1.3&#xff0c;并联比较型工作示意图1.4&#xff0c;逐次逼近型工作示意图1.5&#xff0c;ADC的特性参数1.6&#xff0c;STM32各系列ADC的主要特性 …

51单片机晶振频率与定时中断产生pwn占空比

单片机中晶振频率为12MHZ的机器周期怎么算? 1、系统晶振频率是12M&#xff0c;则机器周期&#xff1d;12&#xff0f;12&#xff1d;1us&#xff1b; 2、定时1ms&#xff1d;1&#xff0a;1000&#xff1d;1000us&#xff1b; 3、工作在方式0下&#xff1a;最大计数值是2&a…

高防IP简介

高防IP可以防御的有包括但不限于以下类型&#xff1a; SYN Flood、UDP Flood、ICMP Flood、IGMP Flood、ACK Flood、Ping Sweep 等攻击。高防IP专注于解决云外业务遭受大流量DDoS攻击的防护服务。支持网站和非网站类业务的DDoS、CC防护&#xff0c;用户通过配置转发规则&#x…

Java之美[从菜鸟到高手演变]之GUI编程(一) 认识Java GUI编程

转眼间一年过去了&#xff0c;自从去年毕业以后博客就没怎么更新过了&#xff0c;一来是因为工作忙没有太多的时间去写&#xff0c;二来可能自己变得比较懒惰&#xff0c;所以就放下了。最近突然想继续整理下Java方面的东西&#xff0c;所以就接着写了。为什么选择Java GUI编程…

php docx,pptx,excel表格上传阿里云,腾讯云存储后截取第一页生成缩略图

php把word转图片的方法:首先给服务器安装libreoffice;然后使用exec函数来调用命令行操作;最后通过“exec(“soffice --headless --invisible…””方法把word转图片即可。 服务器环境:centos7 *集成环境:宝塔 我们开始给服务器安装libreoffice 直接执行下面的代码就可以…

FL Studio 21.2.3.3586 for Mac中文版新功能介绍及2024年最新更新日志

如果你正计划学习音乐制作&#xff0c;一款强大且易学的音乐制作软件是必不可少的。由于很多小伙伴对音乐制作软件没有实际体验过&#xff0c;到底选择哪一款软件最合适成为当下最纠结的问题。 这里为大家推荐一款功能强大且适合新手小伙伴的音乐编曲软件—FL Studio 21.2.3.35…

nginx 模块 高级配置

目录 一、高级配置 1.1. 网页的状态页 1.2.Nginx 第三方模块 ehco 模块 打印 1.3.变量 1.3.1 内置变量 1.3.2自定义变量 1.4.Nginx压缩功能 1.5.https 功能 1.6.自定义图标 一、高级配置 1.1. 网页的状态页 基于nginx 模块 ngx_http_stub_status_module 实现&…