2020-12-25 クリスマスツリーを作るthree.js入門【後編】
https://gyazo.com/c46c5ddeb5a180e93bc5c2cbc38da73e
今回の目標
【前編】three.jsを使ってクリスマスツリーを表示する
【後編】クリスマスツリーを横から360°見ることができる
【後編】クリスマスツリーをクリックしたところに装飾を配置する
後編では、カメラを動かしたり、クリスマスツリーをクリックできるようにします。
code:index.js
const init = () => {
//sceneとかクリスマスツリーのコード
//renderer
const renderer = new THREE.WebGLRenderer({
canvas: mainCanvas,
});
renderer.setSize(windowWidth, windowHeight);
renderer.setPixelRatio(devicePixelRatio);
renderer.render(scene, camera);
//後編はここに書いていきます
}
onload = init;
クリスマスツリーを横から360°見ることができる
https://gyazo.com/716e111bb26789f5c4392693df8d5c4c
クリスマスツリーの周りを1周するように、カメラをx,z軸方向に動かします。
下の図は、クリスマスツリーとカメラの動きを上から見た図です。
https://gyazo.com/346fdf3d14e61af81c25a92871cc9051
カメラの座標は、円周上の座標を求める公式を使います。
原点(x1, y1)、半径r、中央角θ、円周上の座標(x2, y2)とする時、次の公式になります。
(x2, y2) = (x1 + r * cosθ, y1 + r * sinθ)
今回は、原点を(0, 0)とするので、式は次のようになります。
(x2, y2) = (r * cosθ, r * sinθ)
この式を使い、中央角の値を画面更新される(1フレーム)毎に増やし、カメラを動かします。
まず、定数と変数とメソッドを準備します。
code:index.js
//中央角
let cameraAngle = 0;
//円の半径
const cameraRadius = 10;
//1フレームごとに実行するメソッド
const trunCamera = () => {
}
ここからtrunCameraに処理を書いていきます。
次に、中央角の値を増やし、カメラの座標を設定します。
code:index.js
const trunCamera = () => {
cameraAngle += 0.5;
camera.position.x = cameraRadius * Math.sin(cameraAngle * Math.PI / 180);
camera.position.z = cameraRadius * Math.cos(cameraAngle * Math.PI / 180);
次に、カメラの向きを変更します。
カメラを向きをクリスマスツリーの中心を向くようにします。
code:index.js
camera.lookAt(new THREE.Vector3(0, 2.5, 0));
次にレンダリングをします。
code:index.js
renderer.render(scene, camera);
次に画面が更新される度にtrunCameraが実行されるようにします。
code:index.js
requestAnimationFrame(trunCamera);
}
以上がtrunCameraの処理です。
最後に、trunCameraを実行してます。
code:index.js
trunCamera();
以上で、クリスマスツリーの周りを1周するように、カメラをx,z軸方向に動きます。
以下が、「クリスマスツリーを横から360°見ることができる」で書いたコードです。
code:index.js
let cameraAngle = 0;
const cameraRadius = 10;
const trunCamera = () => {
cameraAngle += 0.5;
camera.position.x = cameraRadius * Math.sin(cameraAngle * Math.PI / 180);
camera.position.z = cameraRadius * Math.cos(cameraAngle * Math.PI / 180);
camera.lookAt(new THREE.Vector3(0, 2.5, 0));
renderer.render(scene, camera);
requestAnimationFrame(trunCamera);
}
trunCamera();
クリスマスツリーをクリックしたところに装飾を配置する
https://gyazo.com/bbeab443159c55fdc424b0669520d124
カメラからクリックしたマウスの座標の方向に伸びる半直線が、クリスマスツリーと交差した座標に丸い装飾を配置します。
https://gyazo.com/fbcde1ef7d9252c08b1b593cd5ea7f39
まず、Raycasterとマウスの座標を保存する変数を作成します。
code:index.js
//Raycaster
const raycaster = new THREE.Raycaster();
//マウスの座標
let mouse = new THREE.Vector2();
次に、mousedownイベントを実装します。
ここで、半直線の方向の設定とクリスマスツリーと半直線が交差するかの判定を行います。
code:index.js
window.addEventListener("mousedown", (event) => {
}
mousedownイベントでは、最初に半直線の方向を設定します。
カメラの座標からクリックしたマウスの座標の方向に設定します。
半直線の方向の設定には、raycaster.setFromCamera(マウスの座標, カメラ)を使います。
第1引数のマウスの座標のxとyは、-1から1の値に変換します。
code:index.js
mouse.x = (event.offsetX / window.innerWidth) * 2 - 1;
mouse.y = -(event.offsetY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
次に、半直線と交差するscene内の全てのオブジェクトの情報を取得します。
raycaster.intersectObjectsを使います。
code:index.js
const intersects = raycaster.intersectObjects(scene.children, true);
第1引数は、半直線と交差の判定をするオブジェクトです。
第2引数は、交差の判定にオブジェクトの子孫を含むかのBooleanです。デフォルトは、falseになっています。
ここで取得できる情報は、交差したオブジェクトや交差した座標、半直線の原点からの距離などです。
これらの情報は、交差するオブジェクトが1個以上の場合、半直線の原点からの距離にソートされた状態で配列が取得できます。
(交差するオブジェクトが0個の場合は、空の配列が取得されます。)
次に、半直線とオブジェクトが交差している場合のみ、デコレーションを配置します。
半直線とオブジェクトが交差しているかは、intersectsの要素数で判別します。
また、交差したオブジェクトがクリスマスツリーであるかも判別します。
これは、「【前編】three.jsを使ってクリスマスツリーを表示する」で作成したtreeというグループを使い、判別します。
treeグループとクリスマスツリーの葉、木の幹は、親子関係になっています。
半直線と交差するオブジェクトの親の名前がtreeである場合に、装飾を配置します。
これをすることによって、クリスマスツリーの木の部分のみに装飾を配置することができます。
code:index.js
if (intersects.length > 0 && intersects0.object.parent.name == 'tree') { //装飾を作成
const decoration = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 16, 16),
new THREE.MeshMatcapMaterial({ color: 0xFFD700 })
);
//装飾の座標を半直線と交差した座標に設定
decoration.position.x = intersects0.point.x; decoration.position.y = intersects0.point.y; decoration.position.z = intersects0.point.z; //装飾をシーンに追加
scene.add(decoration);
//レンダリング
renderer.render(scene, camera);
}
以上で完成です。
以下が、「クリスマスツリーをクリックしたところに装飾を配置する」で書いたコードです。
code:index.js
//Raycaster
const raycaster = new THREE.Raycaster();
//マウスの座標
let mouse = new THREE.Vector2();
window.addEventListener("onClick", (event) => {
mouse.x = (event.offsetX / window.innerWidth) * 2 - 1;
mouse.y = -(event.offsetY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
if (intersects.length > 0 && intersects0.object.parent.name == 'tree') { const decoration = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 16, 16),
new THREE.MeshMatcapMaterial({ color: 0xFFD700 })
);
decoration.position.x = intersects0.point.x; decoration.position.y = intersects0.point.y; decoration.position.z = intersects0.point.z; scene.add(decoration);
renderer.render(scene, camera);
}
});
完成したコード
最後にクリスマスツリーを作るthree.js入門の前編、後編で書いたHTMLとJavaScriptのコード全文置いておきます。
code:index.html
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
* {
margin: 0;
padding: 0;
}
</style>
<script src="index.js"></script>
<title>🎄クリスマスツリー🎄</title>
</head>
<body>
<canvas id="mainCanvas"></canvas>
</body>
</html>
code:index.js
const init = () => {
const mainCanvas = document.getElementById('mainCanvas');
const windowWidth = window.innerWidth;
const windowHeight = window.innerHeight;
//scene
const scene = new THREE.Scene();
//light
const light = new THREE.DirectionalLight(0xffffff);
light.intensity = 1;
light.position.set(1, 1, 1);
scene.add(light);
//camera
const camera = new THREE.PerspectiveCamera(45, windowWidth / windowHeight, 0.1, 2000);
camera.position.z = 10;
camera.position.y = 2;
camera.lookAt(new THREE.Vector3(0, 2.5, 0));
//XmasTree
//tree
const tree = new THREE.Group();
tree.name = 'tree';
const trunkGeometry = new THREE.CylinderGeometry(0.5, 0.5, 2);
const trunkMaterial = new THREE.MeshLambertMaterial({ color: 0xbb6600 });
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
tree.add(trunk);
const leafLevel1 = new THREE.Mesh(
new THREE.ConeGeometry(1.5, 2, 100),
new THREE.MeshLambertMaterial({ color: 0x00ff00 })
)
leafLevel1.position.y = 4;
tree.add(leafLevel1);
const leafLevel2 = new THREE.Mesh(
new THREE.ConeGeometry(2, 2, 100),
new THREE.MeshLambertMaterial({ color: 0x00ff00 })
)
leafLevel2.position.y = 3;
tree.add(leafLevel2);
const leafLevel3 = new THREE.Mesh(
new THREE.ConeGeometry(2.7, 2, 100),
new THREE.MeshLambertMaterial({ color: 0x00ff00 })
);
leafLevel3.position.y = 2;
tree.add(leafLevel3);
scene.add(tree);
//star
let starPoints = [], numPoints = 5;
const starScale = 0.025;
for (let i = 0; i < numPoints * 2; i++) {
const l = i % 2 == 1 ? 7 : 20;
const a = i / numPoints * Math.PI;
starPoints.push(new THREE.Vector2(Math.cos(a) * l, Math.sin(a) * l));
}
const shape = new THREE.Shape(starPoints);
const extrudeSetting = {
depth: 5,
};
const starGeometry = new THREE.ExtrudeBufferGeometry(shape, extrudeSetting);
const starMaterial = new THREE.MeshMatcapMaterial({ color: 0xfffb1f });
const star = new THREE.Mesh(starGeometry, starMaterial);
star.scale.x = 0.025;
star.scale.y = 0.025;
star.scale.z = 0.025;
star.rotation.z = 18 * Math.PI / 180;
star.position.y = 5.2;
scene.add(star);
//renderer
const renderer = new THREE.WebGLRenderer({
canvas: mainCanvas,
});
renderer.setSize(windowWidth, windowHeight);
renderer.setPixelRatio(devicePixelRatio);
renderer.render(scene, camera);
//カメラ移動
let cameraAngle = 0;
const cameraRadius = 10;
const trunCamera = () => {
cameraAngle += 1;
camera.position.x = cameraRadius * Math.sin(cameraAngle * Math.PI / 180);
camera.position.z = cameraRadius * Math.cos(cameraAngle * Math.PI / 180);
camera.lookAt(new THREE.Vector3(0, 2.5, 0));
renderer.render(scene, camera);
requestAnimationFrame(trunCamera);
}
trunCamera();
const raycaster = new THREE.Raycaster();
//マウスの座標
let mouse = new THREE.Vector2();
window.addEventListener("mousedown", (event) => {
mouse.x = (event.offsetX / window.innerWidth) * 2 - 1;
mouse.y = -(event.offsetY / window.innerHeight) * 2 + 1;
raycaster.setFromCamera(mouse, camera);
const intersects = raycaster.intersectObjects(scene.children, true);
console.log(intersects);
if (intersects.length > 0 && intersects0.object.parent.name == 'tree') { const decoration = new THREE.Mesh(
new THREE.SphereGeometry(0.1, 16, 16),
new THREE.MeshMatcapMaterial({ color: 0xFFD700 })
);
decoration.position.x = intersects0.point.x; decoration.position.y = intersects0.point.y; decoration.position.z = intersects0.point.z; scene.add(decoration);
renderer.render(scene, camera);
}
});
}
onload = init;