three.js r73 cannon.js 0.6.2 Google Chrome 46.0.2490.86 m |
■three.js + cannon.js で物理演算 [ サンプルページの表示 ] | Prev Top Next | |
|
今回はcannon.jsを使用して物理判定を行います。 まずはcannon.jsをホームページからダウンロードしてください。
cannon.jsはthree.jsと親和性が高く簡単に導入することができます。
なお実装にはCSS-EBLOGを参考にしました。 リンク先の記事はcannon.jsのバージョンが古いため一部、記述を変更していますが、概念を理解するにはとても参考になります。
例によってTypeScript使ってますので PlayGround サイトでJavaScriptにコンパイルしてください。
class myThree { private renderer = null; private camera = null; private scene = null; private width: number = 600; private height: number = 400; // レンダラ―作成 private createRenderer() { var renderer = new THREE.WebGLRenderer(); renderer.setSize( this.width, this.height ); renderer.setClearColor(0xf0f0ff); return renderer; } // カメラ作成 private createCamera() { const fov:number = 60; const aspect:number = this.width / this.height; const znear:number = 1.0; const zfar:number = 1000.0; var camera = new THREE.PerspectiveCamera( fov, aspect, znear, zfar ); // カメラ視点 camera.position.set( 0.0, 0.0, 50.0 ); // カメラの注視点 camera.lookAt( new THREE.Vector3( 0, 0, 0 ) ); return camera; } // テクスチャー作成 private createTexture( path: string ) { var loader = new THREE.TextureLoader(); var texture = loader.load( path ); // テクスチャーフィルター texture.minFilter = THREE.LinearMipMapLinearFilter; texture.magFilter = THREE.LinearMipMapLinearFilter; // 異方性フィルタリング texture.anisotropy = 16; return texture; } // 地面メッシュ作成 public createPlaneMesh() { var texture = this.createTexture( 'Contents/Texture1.png' ); // ラッピング texture.wrapS = THREE.RepeatWrapping; texture.wrapT = THREE.RepeatWrapping; texture.repeat.set( 2, 2 ); var geometry = new THREE.BoxGeometry(50, 5, 50); var material = new THREE.MeshPhongMaterial({ color: 0x00af00, map: texture }); var mesh = new THREE.Mesh(geometry, material); mesh.position.y = -15; mesh.castShadow = false; mesh.receiveShadow = true; this.scene.add( mesh ); return mesh; } // 箱メッシュ作成 public createBoxMesh() { var geometry = new THREE.BoxGeometry(10, 10, 10); var material = new THREE.MeshPhongMaterial({ color: 0x0f0fff }); var mesh = new THREE.Mesh(geometry, material); mesh.position.y = 15; mesh.castShadow = true; mesh.receiveShadow = false; this.scene.add( mesh ); return mesh; } // シーン作成 private createScene() { var scene = new THREE.Scene(); return scene; } // ライト作成 private createLight( lightIndex: number ) { switch( lightIndex ) { // 環境光 // http://threejs.org/docs/#Reference/Lights/AmbientLight case 0: // 環境光の色 var ambientLight = new THREE.AmbientLight( 0x505050 ); return ambientLight; break; // 平行光源 // http://threejs.org/docs/#Reference/Lights/DirectionalLight case 1: // 平行光源の色、強度 var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 ); // 光源から原点に向かう平行光源の方向ベクトル directionalLight.position.set( 1.0, 3.0, -2.0); return directionalLight; break; // 点光源 // http://threejs.org/docs/#Reference/Lights/PointLight case 2: // 点光源の色、強度、減衰率( 0で減衰なし ) var pointLight = new THREE.PointLight( 0xffffff, 1, 0 ); // 点光源の座標 pointLight.position.set(10.0, 10.0, 10.0); return pointLight; break; // 半球ライト // http://threejs.org/docs/#Reference/Lights/HemisphereLight case 3: // 空の色、地面の色、強度 var hemisphereLight = new THREE.HemisphereLight( 0xa0a0ff, 0xa0ffa0, 1 ); return hemisphereLight; break; // スポットライト // http://threejs.org/docs/#Reference/Lights/SpotLight case 4: // 色、強度、光が届く距離、光の範囲( 最大値:Math.PI/2 )、減衰率、光が暗くなる量 var spotLight = new THREE.SpotLight(0xffffff, 1, 40, Math.PI / 3, 2, 0.5); // ライトの視点 spotLight.position.set(5.0, 10.0, 0.0); // ライトの注視点 spotLight.lookAt( new THREE.Vector3( 0, 0, 0 ) ); return spotLight; break; default: alert( "無効なライトです" ); break; } return null; } // Three作成 public createTHREE() { if( this.renderer == null ) { this.renderer = this.createRenderer(); document.body.appendChild( this.renderer.domElement ); } this.renderer.shadowMap.enabled = true; this.renderer.shadowMap.type = 0; if( this.camera == null ) { this.camera = this.createCamera(); } this.scene = this.createScene(); var light = this.createLight(1); light.castShadow = true; // シャドウマップ生成時に使用するライトとして設定 light.shadowCameraNear = 1; // 射影行列の Near light.shadowCameraFar = 250; // 射影行列の Far light.shadowCameraFov = 50; // 射影行列の Fov( 視野角 ) light.shadowBias = 0.0001; // 影判定用のバイアス light.shadowMapWidth = 2048; // シャドウマップの横幅 light.shadowMapHeight = 2048; // シャドウマップの縦幅 this.scene.add(light); var light2 = this.createLight(0); this.scene.add(light2); } // レンダリング public render() { if( this.renderer != null ) { this.renderer.render( this.scene, this.camera ); } } public dispose() { if( this.renderer != null ) { var element = document.getElementsByTagName("canvas"); if( element.length == 1 ) { document.body.removeChild(element[0]); } this.renderer.dispose(); this.renderer = null; } this.camera = null; } } class myCannon { private world = null; public createWorld() { // CANNONの世界を生成 this.world = new CANNON.World(); // 重力加速度を設定 this.world.gravity.set(0, -9.82, 0); // 衝突している可能性のある剛体同士を見つける。 // 本当に衝突しているか、衝突しているとしてどの程度めり込んでいるかは別途ナローフェーズで行う。 this.world.broadphase = new CANNON.NaiveBroadphase(); // 反復計算回数 this.world.solver.iterations = 10; // 許容値。ある程度の誤差を許容することで不安定さを解消する。 this.world.solver.tolerance = 0.1; } public createPlane(width:number, height:number, depth:number, position:CANNON.Vec3, quaternion:CANNON.Quaternion) { var plane = new CANNON.Box(new CANNON.Vec3(width, height, depth)); // 質量 var mass:number = 0; var body = new CANNON.Body({mass:mass}); body.position.copy( position ); body.quaternion.copy( quaternion ); body.addShape( plane ); this.world.add( body ); return body; } public createBox(width:number, height:number, depth:number, position:CANNON.Vec3, quaternion:CANNON.Quaternion) { var box = new CANNON.Box(new CANNON.Vec3(width, height, depth)); // 質量 var mass:number = 1; var body = new CANNON.Body({mass:mass}); body.position.copy( position ); body.quaternion.copy( quaternion ); body.addShape( box ); // z軸に1の角速度 body.angularVelocity.set(0, 0, 1); // 回転の減衰係数 body.angularDamping = 0.1; this.world.add( body ); return body; } // FPS:1/60で更新 public animationWorld() { this.world.step( 1/60 ); } } class FPS { private dispFrame: number = 0; private frame: number = 0; private timer: Date = new Date(); public getFPS() { var now = new Date(); // 1秒経過してない if( now.getTime() - this.timer.getTime() < 1000 ) { this.frame++; } else { this.timer = now; this.dispFrame = this.frame; this.frame = 0; } return this.dispFrame; } } var mythree = new myThree(); var fps = new FPS(); var mesh = []; var mycannon = new myCannon(); var body = []; // cannon.jsで座標と姿勢を計算した値をthree.jsにコピーする function animationMesh() { for( var i=0; i<mesh.length; i++ ) { mesh[i].position.copy( body[i].position ); mesh[i].quaternion.copy( body[i].quaternion ); } } // シーンの初期化 window.addEventListener('load', () => { if( mythree != null ) { mythree.createTHREE(); mesh[0] = mythree.createPlaneMesh(); mesh[1] = mythree.createBoxMesh(); } if( mycannon != null ) { mycannon.createWorld(); // THREE.jsと異なり半分の大きさで指定する body[0] = mycannon.createPlane(mesh[0].geometry.parameters.width / 2, mesh[0].geometry.parameters.height / 2, mesh[0].geometry.parameters.depth / 2, mesh[0].position, mesh[0].quaternion); body[1] = mycannon.createBox(mesh[1].geometry.parameters.width / 2, mesh[1].geometry.parameters.height / 2, mesh[1].geometry.parameters.depth / 2, mesh[1].position, mesh[1].quaternion); } }); // メインループ window.addEventListener('load', () => { ( function renderLoop () { requestAnimationFrame( renderLoop ); mycannon.animationWorld(); animationMesh(); mythree.render(); // FPS表示 var frame = fps.getFPS(); $("div.fps").html( "FPS:" + frame ); } )(); });