hands-on three.js + cannon.js - end0tknr's kipple - web写経開発 の更に前段です。
目次
1. cubeのグルーピング
以下の実際の動作は、github pagesをご覧ください。 https://end0tknr.github.io/sandbox/threejs_cannonjs_misc/test_threejs.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/109/three.min.js"></script> <script src="https://cdn.jsdelivr.net/gh/mrdoob/three.js@r109/examples/js/controls/OrbitControls.js"></script> </head> <body> <div id="stage"></div> <script> (function() { 'use strict'; var width = 500; var height = 250; var scene = new THREE.Scene(); // mesh 物体 var head = new THREE.Mesh( new THREE.BoxGeometry(20, 20, 20), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); head.position.set(0, 40, 0); // scene.add(head); var body = new THREE.Mesh( new THREE.BoxGeometry(40, 60, 40), new THREE.MeshLambertMaterial({ color: 0xff0000 }) ); body.position.set(0, 0, 0); // scene.add(body); // 頭部と体をグループ化 var person = new THREE.Group(); person.add(head); person.add(body); scene.add(person); // light var light = new THREE.DirectionalLight(0xffffff, 1); light.position.set(0, 100, 30); scene.add(light); // 環境光源 var ambient = new THREE.AmbientLight(0x404040); scene.add(ambient); // camera var camera = new THREE.PerspectiveCamera(45, width / height, 1, 1000); camera.position.set(200, 100, 300); camera.lookAt(scene.position); // helper var gridHelper = new THREE.GridHelper(200, 50); scene.add(gridHelper); var axisHelper = new THREE.AxisHelper(1000); scene.add(axisHelper); var lightHelper = new THREE.DirectionalLightHelper(light, 20); scene.add(lightHelper); // controls - マウスによる操作 var controls = new THREE.OrbitControls(camera); // controls.autoRotate = true; // renderer var renderer = new THREE.WebGLRenderer({ antialias: true }); renderer.setSize(width, height); renderer.setClearColor(0xefefef); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById('stage').appendChild(renderer.domElement); function render() { requestAnimationFrame(render); person.rotation.y += 0.01; controls.update(); renderer.render(scene, camera); } render(); })(); </script> </body> </html>
2. 様々な形状の表示
https://ozateck.sakura.ne.jp/wordpress/category/three-js/
上記urlからの写経です。
https://end0tknr.github.io/sandbox/threejs_cannonjs_shimeji/index.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <!-- refer to https://ozateck.sakura.ne.jp/wordpress/ --> <title>Hello Three.js</title> <style> body{ margin: 0; overflow: hidden; } </style> </head> <body> <div id="stage"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js"></script> <!-- 処理速度やフレームレートを確認する為 --> <script src="https://cdn.jsdelivr.net/npm/stats-js@1.0.1/build/stats.min.js"></script> <!-- マウスによるカメラ操作 --> <script src="https://cdn.jsdelivr.net/npm/three@0.105.2/examples/js/controls/TrackballControls.js"> </script> <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script> <script src="js/app.js"></script> </body> </html>
// refer to http://ozateck.sakura.ne.jp/wordpress var width = 480; var height = 320; var fov = 60; var aspect = width / height; var near = 1; var far = 1000; //座標系 Z↑ /Y // │/ // 原点─→X // Scene - 各オブジェクト(円、四角等)を表示 var scene = new THREE.Scene(); // Axes - 空間のx, y, zを原点から表す var axes = new THREE.AxisHelper(20); scene.add(axes); // Camera - 空間の視点をを決める. // コンストラクタの引数はそれぞれ、 // 視野角(fov)、縦横幅比率(aspect)、描画領域(近い方)、描画領域(遠い方) var camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.set(0, 100, 200); camera.lookAt(scene.position); // Controls - マウスによる操作. // google chromeで以下のerrorとなる場合、その更に下のurlが参考になります。 // [Intervention] Unable to preventDefault inside passive event listener // due to target being treated as passive. // https://note.com/cfbif/n/n92195df174bf var controls = new THREE.TrackballControls(camera); controls.rotateSpeed = 5.0; //回転速度 controls.zoomSpeed = 0.5;//ズーム速度 controls.panSpeed = 2.0;//パン速度 // Light - 空間への光の方向を決める var directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(0, 0.7, 0.7); scene.add(directionalLight); // Stats - 処理速度やフレームレートを確認する為 var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = "absolute"; stats.domElement.style.left = "0px"; stats.domElement.style.top = "0px"; document.getElementById("stage").appendChild(stats.domElement); // Plane // Three.js のobjectには、Geometry, Material, Mesh を使う。 // Geometryはオブジェクトの形や大きさ. Materialにはオブジェクトの色や質感を表す。 // Meshオブジェクトに、これらの2つのオブジェクトを指定しインスタンス化 var geometry = new THREE.PlaneGeometry(200, 250); var material = new THREE.MeshBasicMaterial({color: 0x666666}); var plane = new THREE.Mesh(geometry, material); plane.position.set(0, 0, 0); plane.rotation.set(-90 * Math.PI / 180, 0, 0); scene.add(plane); // Particles var geometry = new THREE.Geometry(); var material = new THREE.PointsMaterial({color: 0xffffff, size: 4, vertexColors: true}); for(var x=0; x<10; x++){ for(var y=0; y<10; y++){ var particle = new THREE.Vector3(x*10, y*10, 0); geometry.vertices.push(particle); geometry.colors.push(new THREE.Color(Math.random() * 0x00ffff)); } } // 複数の点が、1コとobjectとして扱われます var points = new THREE.Points(geometry, material); scene.add(points); // Cubeを作成し、その後、順に削除 for(var i=0; i<20; i++){ var x = getRandom(-80, 80); var y = getRandom( 0, 80); var z = getRandom(-80, 80); var mesh = new THREE.Mesh( new THREE.BoxBufferGeometry(10,10,10), new THREE.MeshBasicMaterial({color: 0xccffcc, wireframe: true}) ); mesh.position.set(x, y, z); mesh.name = "tmpCube"; scene.add(mesh); } function getRandom(min, max){ return Math.floor(Math.random()*(max-min+1))+min; } function removeCube(){ var total = scene.children.length; for(var i=0; i<total; i++){ var obj = scene.children[i]; if(obj.name == "tmpCube"){ scene.remove(obj); return; } } } setInterval(removeCube, 2000); // Cube var geometry = new THREE.BoxGeometry(30, 30, 30); var material = new THREE.MeshNormalMaterial(); var cube = new THREE.Mesh(geometry, material); cube.position.set(0, 50, 0); scene.add(cube); // Earth var txLoader = new THREE.TextureLoader(); var earth = null; txLoader.load( "img/earth.jpg", function(texture){ var geometry = new THREE.SphereGeometry(30, 30, 30); var material = new THREE.MeshBasicMaterial({map:texture, overdraw:0.5}); earth = new THREE.Mesh(geometry, material); earth.position.set(-30, 50, 100); scene.add(earth); }); // Moon var moon = null; txLoader.load( "img/moon.jpg", function(texture){ var geometry = new THREE.SphereGeometry(10, 10, 10); var material = new THREE.MeshBasicMaterial({map:texture, overdraw:0.5}); moon = new THREE.Mesh(geometry, material); moon.position.set(50, 50, 50); // moon.position.set(100, 50, 0); scene.add(moon); }); // Renderer - 毎フレーム描画 var renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(width, height); renderer.setClearColor(0xcccccc); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById("stage").appendChild(renderer.domElement); // Radian - 360 = 2 * PI , 180 = PI var radius = 50; var degree = 0; // Loop loop(); function loop(){ stats.update(); // Earth, Moon を回転 degree += 0.5; if(360 <= degree) degree = 0; var radian = degree * Math.PI / 180; var x = radius * Math.cos(radian); var y = radius * Math.sin(radian); if(earth != null){ earth.rotation.set(0, radian, 0); } if(moon != null){ moon.rotation.set(0, radian, 0); // 以下の x,y を入れ替えると、逆向きに回転します moon.position.set(y-30, 50, x+50); } // Cube を回転 cube.rotation.x += 0.05; cube.rotation.y += 0.05; controls.update(); renderer.render(scene, camera); window.requestAnimationFrame(loop ); //再帰呼び出し };
3. 音声の可視化
https://ozateck.sakura.ne.jp/wordpress/category/three-js/
先程と同様、上記urlからの写経です。
ブラウザ経由で、PCのマイクを使用しますので、 PCに向かって、手を叩くと、緑色の部分に波形として現れます。
ただし、navigator.getUserMedia() は、depricated のようですので、 そのうち、navigator.mediaDevices.getUserMedia() へ書き換える必要があります。
https://end0tknr.github.io/sandbox/threejs_cannonjs_shimeji/index_audio.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <!-- refer to https://ozateck.sakura.ne.jp/wordpress/ --> <title>Hello Three.js</title> <style> body{ margin: 0; overflow: hidden; } </style> </head> <body> <h1>マイクからの入力音声を波形として表示</h1> <div id="stage"></div> <audio muted></audio> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js"></script> <!-- 処理速度やフレームレートを確認する為 --> <script src="https://cdn.jsdelivr.net/npm/stats-js@1.0.1/build/stats.min.js"></script> <!-- マウスによるカメラ操作 --> <script src="https://cdn.jsdelivr.net/npm/three@0.105.2/examples/js/controls/TrackballControls.js"> </script> <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script> <script src="js/app_audio.js"></script> </body> </html>
console.log("Hello Three.js!!"); var width = 480; var height = 320; var fov = 60; var aspect = width / height; var near = 1; var far = 1000; // Scene var scene = new THREE.Scene(); // Axes var axes = new THREE.AxisHelper(20); scene.add(axes); // Stats var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = "absolute"; stats.domElement.style.left = "0px"; stats.domElement.style.top = "0px"; document.getElementById("stage").appendChild(stats.domElement); // Plane var geometry = new THREE.PlaneGeometry(100, 200); var material = new THREE.MeshBasicMaterial({color: 0x666666}); var plane = new THREE.Mesh(geometry, material); plane.position.set(0, 0, 0); plane.rotation.set(-90 * Math.PI / 180, 0, 0); scene.add(plane); // Camera var camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.set(0, 100, 200); camera.lookAt(scene.position); // Light var directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(0, 0.7, 0.7); scene.add(directionalLight); // Renderer var renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(width, height); renderer.setClearColor(0x333333); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById("stage").appendChild(renderer.domElement); //========== // Web Audio navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; navigator.getUserMedia({audio : true}, onSuccess, onError); function onSuccess(stream){ console.log("onSuccess"); // document.querySelector("audio").src = URL.createObjectURL(stream); // ↑old. ↓new document.querySelector("audio").src = window.URL.createObjectURL(new Blob([stream], {type: "application/zip"})) var audioContext = new AudioContext(); var analyser = audioContext.createAnalyser(); var timeDomain = new Float32Array(analyser.frequencyBinCount); var frequency = new Uint8Array(analyser.frequencyBinCount); audioContext.createMediaStreamSource(stream).connect(analyser); console.log("frequency:" + frequency.length); loop(); function loop(){ analyser.getFloatTimeDomainData(timeDomain); analyser.getByteFrequencyData(frequency); // update updateThree(timeDomain, frequency); window.requestAnimationFrame(loop); } } function onError(e){ console.log("onError:" + e); } // Points var points; // Draw function updateThree(timeDomain, frequency){ // Stats stats.update(); // Remove Points if(points != null) scene.remove(points); // Particles var geom = new THREE.Geometry(); var material = new THREE.PointsMaterial({ color: 0xffffff, size: 2, vertexColors: true, transparent: true, opacity: 1.0 }); var paddingX = 2; // パーティクルの間隔 var offset = 4; // 1024を4分割 var total = Math.floor(frequency.length / offset); var minX = total * paddingX / -2; // 開始位置x var maxY = 100;// 最大の高さy console.log(total); for(var i=0; i<total; i++){ var particle = new THREE.Vector3( minX + i * paddingX, Math.max(0, frequency[i*offset] * maxY / 255), 0); geom.vertices.push(particle); var color = new THREE.Color(0x00ff00); color.setHSL(color.getHSL().h, color.getHSL().s, color.getHSL().l); geom.colors.push(color); } // Add Points points = new THREE.Points(geom, material); scene.add(points); // Render renderer.render(scene, camera); }
4. 音声の可視化 その2
「音声の可視化 」の別バージョンです。
PCマイクに向かって、手をたたくと、cubeが追加されます。
反応が悪いようですが、src修正は実施していません。
https://end0tknr.github.io/sandbox/threejs_cannonjs_shimeji/index_audio_2.html
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <!-- refer to https://ozateck.sakura.ne.jp/wordpress/ --> <title>Hello Three.js</title> <style> body{ margin: 0; overflow: hidden; } </style> </head> <body> <h1>マイクからの(拍手)入力で、Cubeを追加</h1> 反応が悪いようですが、js srcは修正していません。 <div id="stage"></div> <audio muted></audio> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/97/three.min.js"></script> <!-- 処理速度やフレームレートを確認する為 --> <script src="https://cdn.jsdelivr.net/npm/stats-js@1.0.1/build/stats.min.js"></script> <!-- マウスによるカメラ操作 --> <script src="https://cdn.jsdelivr.net/npm/three@0.105.2/examples/js/controls/TrackballControls.js"> </script> <script src="https://cdn.jsdelivr.net/npm/cannon@0.6.2/build/cannon.min.js"></script> <script src="js/app_audio_2.js"></script> </body> </html>
console.log("Hello Three.js!!"); var width = 480; var height = 320; var fov = 60; var aspect = width / height; var near = 1; var far = 1000; // Scene var scene = new THREE.Scene(); // Axes var axes = new THREE.AxisHelper(20); scene.add(axes); // Stats var stats = new Stats(); stats.setMode(0); stats.domElement.style.position = "absolute"; stats.domElement.style.left = "0px"; stats.domElement.style.top = "0px"; document.getElementById("stage").appendChild(stats.domElement); // Plane var geometry = new THREE.PlaneGeometry(100, 200); var material = new THREE.MeshBasicMaterial({color: 0x666666}); var plane = new THREE.Mesh(geometry, material); plane.position.set(0, 0, 0); plane.rotation.set(-90 * Math.PI / 180, 0, 0); scene.add(plane); // Camera var camera = new THREE.PerspectiveCamera(fov, aspect, near, far); camera.position.set(0, 100, 200); camera.lookAt(scene.position); // Light var directionalLight = new THREE.DirectionalLight(0xffffff); directionalLight.position.set(0, 0.7, 0.7); scene.add(directionalLight); // Renderer var renderer = new THREE.WebGLRenderer({antialias: true}); renderer.setSize(width, height); renderer.setClearColor(0x333333); renderer.setPixelRatio(window.devicePixelRatio); document.getElementById("stage").appendChild(renderer.domElement); //========== // Web Audio navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; navigator.getUserMedia({audio : true}, onSuccess, onError); function onSuccess(stream){ console.log("onSuccess"); //document.querySelector("audio").src = URL.createObjectURL(stream); // ↑old. ↓new document.querySelector("audio").src = window.URL.createObjectURL(new Blob([stream], {type: "application/zip"})) var audioContext = new AudioContext(); var analyser = audioContext.createAnalyser(); var timeDomain = new Float32Array(analyser.frequencyBinCount); var frequency = new Uint8Array(analyser.frequencyBinCount); audioContext.createMediaStreamSource(stream).connect(analyser); //========== // 拍手を検出するクラス // 第一引数:次の拍手までの待機時間(ミリ秒) // 第二引数:反応する拍手音量の閾値 var cManager = new CrapManager(500, 2); loop(); function loop(){ // Analiser analyser.getFloatTimeDomainData(timeDomain); analyser.getByteFrequencyData(frequency); // Stats stats.update(); // 拍手の判定 if(cManager.trigger(timeDomain, frequency) == true){ console.log("start addCube()"); // Cubeを追加する addCube(); } // Render renderer.render(scene, camera); // Request window.requestAnimationFrame(loop); } } function onError(e){ console.log("onError:" + e); } //========== // Cubeをランダムで配置する関数 function addCube(){ var x = getRandom(-80, 80); var y = getRandom(0, 80); var z = getRandom(-80, 80); var cube = createCube(5, 0xffffff, x, y, z); scene.add(cube); } //========== // Cubeを指定のサイズ、色、座標で作り出す関数 function createCube(size, color, x, y, z){ var geometry = new THREE.BoxBufferGeometry(size, size, size); var material = new THREE.MeshBasicMaterial({color: 0xccffcc, wireframe: true}); var mesh = new THREE.Mesh(geometry, material); mesh.position.set(x, y, z); return mesh; } function getRandom(min, max){ return Math.floor(Math.random()*(max-min+1))+min; } //========== // CrapManager class CrapManager{ constructor(interTime, interThreshold){ this.interTime = interTime; this.interThreshold = interThreshold; this.interFlg = false; } trigger(timeDomain, frequency){ if(this.interFlg == true) return false; var offset = 30; var total = Math.floor(frequency.length / offset); var volume = 0; for(var i=0; i<total; i+=offset){ volume += frequency[i * offset]; } if(volume < this.interThreshold) return false; this.interFlg = true; setInterval(()=>{this.interFlg = false;}, this.interTime); return true; } }