Three.js r146 と cannon-es v0.20.0 で物理エンジンとカメラ回転のテスト
前回のnoteで、できそうにないな~、と書いていたモンハンライクなカメラの機能ができたので記録として関連コードとメモを貼り付けておきます。
モンハンカメラといっても、カメラリセット時にカメラが操作対象3Dオブジェクトの背後(真後ろ)にまわるだけのものですが、自分は長い間ウンウンどうやったらできるんや~、と悩んでいたのでなかなかの達成感ありです。
そんな自己満足気味なnoteエントリですw。
成果物概要
で、その最終成果物は以下になります。 モンハンカメラのために実装しているコードは、自分でも本当にこんな流れの処理でえーんか?、と思うようなものですが、とりあえず動いたのでOKとして最終成果物としていますw。
Three.js r146 + cannon-es v0.20.0 Test10 Physics Engine & Camera Rotation(コード表示付き)
Three.js r146 + cannon-es v0.20.0 Test10 Physics Engine & Camera Rotation( full page view )
成果物ソースコード
前回と同じように自分が後で参照する用機能もかねて、ソースコードも載せておきます。 以下となります。
HTML
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<button id="forwardButton">forward</button>
<button id="leftRotButton">leftRot</button>
<button id="rightRotButton">rightRot</button>
<button id="backwardButton">backward</button>
<br>
<button id="camLeftRotButton">camLeftRot</button>
<button id="camRightRotButton">camRightRot</button>
<button id="camHeightButton">camHeight</button>
<button id="camDistButton">camDist</button>
<button id="camResetButton">camReset</button>
</body>
</html>
JavaScript
今回も、よく理解していないベクトルとクォータニオンをごちゃごちゃまぜてのJavaScriptコード作成ですw。
モンハンカメラのリセット機能は、最初に操作オブジェクト&カメラのベクトル&クォータニオンによりカメラ位置計算後に、クォータニオン → 弧度法 → 度数法 → 調整いろいろ、と変換してカメラ位置を設定しています。
これ以外のやり方が思いつかなかったのでとりあえずこの方法で実装していますが、この流れの処理で本当にいいのかは自分でもかなり疑問ですw。
(自分の理解の範囲で)参考 3Dでの角度いろいろ
度数法 → 90度とか180度とか360度とか。
弧度法 → πとか2πとかラジアンとか。
クォータニオン → 謎。 4つぐらいパラメータとったりする。 Three.jsでは度数法や弧度法からの相互変換のライブラリが整備されているよう。 3D計算のためのベクトルさんにはやさしい存在? ジンバルロックとかゆう問題も解決してくれるらしい。 (自分的には)理論となる数式は見ちゃダメだ、ライブラリ使おう&頼ろう。
コードの
if ( camResetButtonOn ) {
}
でかこまれた部分での(3Dワイヤーフレーム立方体による)操作オブジェクトの背後にまわるカメラの位置・角度を計算しています。
//three.jsと(cannon.jsベースの)cannon-esの連携をテストしてみました
//
//前回コードからカメラ機能をつけてみました
//
//これまでの「camRest」ボタンを改善して、「camRest」ボタン押下時に
//カメラが操作対象3Dオブジェクトの真後ろに位置するようにコードしてみました
//動くようにはなりましたが、なぜそうなるのか理解していない部分いっぱいw
//
//
//自分の作成したものも含めて、以下サイト・コードを参考にさせていただきました
//
//
//角度について参考にさせていただいたサイト
//
//WebGL入門 ~ three.js ③~
//https://zenn.dev/vava/articles/96c9b644665670
//より
//radian = degree / 360 * 2π = degree * Math.PI / 180
//
//webgl - three.js
//http://webgl.akjava.com/three-js/rotation
//
//Three.js で オブジェクト の角度 を取得したいです
//https://teratail.com/questions/349531
//より
//angle = THREE.MathUtils.radToDeg(e.y)
//
//
//
//
//コード作成時に引用・参考にした自分作成のコードいろいろ
//
//Three.js r146 + cannon-es v0.20.0 Test04
//https://codepen.io/siouxcitizen/pen/rNrNBwG
//から派生させたコードを元に、
//
//Three.js r146 + cannon-es v0.20.0 Test07 Camera Rotation Test
//https://codepen.io/siouxcitizen/pen/XWBWzmB
//を参考にしつつ、
//
//Three.js r146 + cannon-es v0.20.0 Test08
//https://codepen.io/siouxcitizen/pen/KKBrMEp
//で「camRest」ボタンの機能をつくりはじめ
//
//
//Three.js r146 + cannon-es v0.20.0 Test09
//https://codepen.io/siouxcitizen/pen/bGjOwjd
//でなんとか想定した「camRest」ボタンによる動きのできるコードとなりました
//
//
//この 「 Three.js r146 + cannon-es v0.20.0 Test10 」 は
//そのTest09のコードを、コメントを追加しつつ少し整理整頓したものです
//
import * as CANNON from 'https://cdn.jsdelivr.net/npm/cannon-es@0.20.0/dist/cannon-es.min.js';
import * as THREE from 'https://unpkg.com/three@0.146.0/build/three.module.js';
// Cannon.js 変数
let world;
let phyGroundBox;
let phyGroundBoxMat
let phySphere;
let phySphereMat;
let phyBox;
let phyBoxMat;
let phyWireBox;
let phyWireBoxMat;
let phyWireBoxAndPhyGroundBoxCM;
// Three.js 変数
let camera, scene, renderer;
let viewGroundBox;
let viewSphere;
let viewBox;
let viewWireBox;
// ボタン押下イベント受け用
// 操作用3Dオブジェクト操作ボタン用
const forwardButton = document.getElementById('forwardButton');
const leftRotButton = document.getElementById('leftRotButton');
const rightRotButton = document.getElementById('rightRotButton');
const backwardButton = document.getElementById('backwardButton');
// カメラ操作ボタン用
const camLeftRotButton = document.getElementById('camLeftRotButton');
const camRightRotButton = document.getElementById('camRightRotButton');
const camHeightButton = document.getElementById('camHeightButton');
const camDistButton = document.getElementById('camDistButton');
const camResetButton = document.getElementById('camResetButton');
//ボタン押下状態保持用
let forwardButtonOn = false;
let leftRotButtonOn = false;
let rightRotButtonOn = false;
let backwardButtonOn = false;
let camLeftRotButtonOn = false;
let camRightRotButtonOn = false;
let camResetButtonOn = false;
let rot = 0; // 角度
//let targetRot = 0;
let targetRot = 180;
// カメラの高さに関する変数・定数の設定
// 高さの最小値
const MIM_CAMERA_HEIGHT = 5;
// 高さの最大値
const MAX_CAMERA_HEIGHT = 25;
// 高さの初期値
const DEFFAULT_CAMERA_HEIGHT = 5;
// 高さの計算単位
const CAMERA_HEIGHT_CALC_UNIT = 5;
// 高さの計算に使用する変数
// 高さの初期値を設定
let cameraHeight = DEFFAULT_CAMERA_HEIGHT;
// カメラの操作対象3Dオブジェクトからの距離に関する変数・定数の設定
// 距離の最小値
const MIM_CAMERA_DIST = 15;
// 距離の最大値
const MAX_CAMERA_DIST = 30;
// 距離の初期値
const DEFFAULT_CAMERA_DIST = 25;
// 距離の計算単位
const CAMERA_DIST_CALC_UNIT = 5;
// 距離の計算に使用する変数
// 距離の初期値を設定
let cameraDistance = DEFFAULT_CAMERA_DIST;
// 操作用3Dオブジェクトの座標初期値
const OP_OBJECT_DEFAULT_POS_X = 0;
const OP_OBJECT_DEFAULT_POS_Y = 10;
const OP_OBJECT_DEFAULT_POS_Z = 15;
//Three.jsのClockを使用してフレームごとの経過時間を正確に取得用
let clock = new THREE.Clock();
let delta;
//実験中の変数
let inputVelocity = new THREE.Vector3();
let velocityFactor = 0.2;
let euler = new THREE.Euler();
//let pitchObject = new THREE.Object3D();
let yawObject = new THREE.Object3D();
let quaternion = new THREE.Quaternion();
initThree();
initCannon();
animate();
setupEventHandlers();
function initCannon() {
// Cannon.jsの物理空間を生成
world = new CANNON.World();
// 重力を設定
world.gravity.set(0, -9.82, 0);
// ぶつかっている「可能性のある」剛体同士を見つける作業
world.broadphase = new CANNON.NaiveBroadphase();
// 反復計算回数
world.solver.iterations = 5;
// 許容値
world.solver.tolerance = 0.1;
// 地面をBoxで作成
// 摩擦等の設定のための地面Boxのマテリアル
phyGroundBoxMat = new CANNON.Material('phyGroundBoxMat');
// Boxシェイプの剛体を質量0で生成、摩擦設定のためのマテリアルを指定
phyGroundBox = new CANNON.Body({
mass: 0,
material: phyGroundBoxMat
});
phyGroundBox.addShape(new CANNON.Box(new CANNON.Vec3(7.5, 0.1, 7.5)));
// 減衰率
phyGroundBox.angularDamping = 0.99;
// 物理世界に追加
world.addBody(phyGroundBox);
// Sphereを作成
// 摩擦等の設定のためのSphereマテリアル
phySphereMat = new CANNON.Material('phySphereMat');
// Sphereシェイプの剛体を質量1で生成、摩擦設定のためのマテリアルを指定
phySphere = new CANNON.Body({
mass: 1,
material: phySphereMat
});
phySphere.addShape(new CANNON.Sphere(1));
// 剛体の位置を設定
phySphere.position.set(-5, 10, 0);
// Z軸に1の角速度を設定
phySphere.angularVelocity.set(0, 0, 1);
// 減衰率
phySphere.angularDamping = 0.1;
// 物理世界に追加
world.addBody(phySphere);
// Boxを作成
// 摩擦等の設定のためのBoxマテリアル
phyBoxMat = new CANNON.Material('phyBoxMat');
// Boxシェイプの剛体を質量50で生成、摩擦設定のためのマテリアルを指定
phyBox = new CANNON.Body({
mass: 50,
material: phyBoxMat
});
phyBox.addShape(new CANNON.Box(new CANNON.Vec3(2, 2, 2)));
// 剛体の位置を設定
phyBox.position.x = 5;
phyBox.position.y = 10;
//phyBox.position.z = 0;
// X軸に100の角速度を設定
phyBox.angularVelocity.set(100, 0, 0);
// 減衰率
phyBox.angularDamping = 0.99;
// 物理世界に追加
world.addBody(phyBox);
// Box(ワイヤーフレームBox用)を作成
// 摩擦等の設定のためのBoxマテリアル
phyWireBoxMat = new CANNON.Material('phyWireBoxMat');
// Boxシェイプの剛体を質量1で生成、摩擦設定のためのマテリアルを指定
phyWireBox = new CANNON.Body({
mass: 1,
material: phyWireBoxMat
});
const shape = new CANNON.Box(new CANNON.Vec3(1, 1, 1));
phyWireBox.addShape(shape);
// 剛体の位置を設定
phyWireBox.position.x = -1;
phyWireBox.position.y = 10;
//phyWireBox.position.z = 0;
//phyWireBox.fixedRotation = true;
// Y軸に1の角速度を設定
//phyWireBox.angularVelocity.set(0, 1, 0);
// 減衰率
//phyWireBox.angularDamping = 0.5;
phyWireBox.angularDamping = 1;
// 物理世界に追加
world.addBody(phyWireBox);
// Material同士の接触設定
// phyWireBoxとphyGroundBoxが接触した際のContactMaterialを生成
phyWireBoxAndPhyGroundBoxCM = new CANNON.ContactMaterial(
phyGroundBoxMat, //ひとつ目のマテリアル(地面用Boxのマテリアル)
phyWireBoxMat, //ふたつ目のマテリアル(操作用ワイヤーフレームBoxのマテリアル)
{
contactEquationRelaxation: 3, // 接触式の緩和性
contactEquationStiffness: 10000000, // 接触式の剛性
friction: 0.001, //摩擦係数
frictionEquationRelaxation: 3, // 摩擦式の剛性
frictionEquationStiffness: 10000000, // 摩擦式の緩和性
restitution: 0.1 // 反発係数
}
);
// 生成したContactMaterialをworldに追加
world.addContactMaterial(phyWireBoxAndPhyGroundBoxCM);
}
function initThree() {
// Three.jsのSceneを生成
scene = new THREE.Scene();
// Three.jsのカメラを生成
//camera = new THREE.PerspectiveCamera(30, 650 / 400, 1, 10000);
camera = new THREE.PerspectiveCamera( 30, window.innerWidth/window.innerHeight, 0.2, 5000 );
camera.position.set(0, 5, -25);
camera.lookAt(new THREE.Vector3(0, 0, 0));
scene.add(camera);
// Three.jsのライトを生成
let light = new THREE.DirectionalLight(0xffffff, 2);
light.position.set(10, 10, -10);
light.castShadow = true;
light.shadowMapWidth = 1024;
light.shadowMapHeight = 1024;
light.shadowCameraLeft = -10;
light.shadowCameraRight = 10;
light.shadowCameraTop = 10;
light.shadowCameraBottom = -10;
light.shadowCameraFar = 100;
light.shadowCameraNear = 0;
light.shadowDarkness = 0.5;
scene.add(light);
let amb = new THREE.AmbientLight(0x999999);
scene.add(amb);
// Three.jsのBoxで地面用Boxの見た目を作成
viewGroundBox = new THREE.Mesh(
new THREE.BoxGeometry(15, 0.2, 15),
new THREE.MeshLambertMaterial(
{
color: 0xffff00
}
)
);
viewGroundBox.castShadow = true;
viewGroundBox.receiveShadow = true;
scene.add(viewGroundBox);
// Three.jsでSphereの見た目を作成
viewSphere = new THREE.Mesh(
new THREE.SphereGeometry(1, 50, 50),
new THREE.MeshLambertMaterial(
{ color: 0xff0000 }
)
);
viewSphere.castShadow = true;
viewSphere.receiveShadow = true;
scene.add(viewSphere);
// Three.jsでBoxの見た目を作成
viewBox = new THREE.Mesh(
new THREE.BoxGeometry(4, 4, 4),
new THREE.MeshLambertMaterial(
{ color: 0x0000ff }
)
);
viewBox.castShadow = true;
viewBox.receiveShadow = true;
scene.add(viewBox);
// Three.jsでワイヤーフレームのBoxの見た目を生成
const geometry = new THREE.BoxBufferGeometry(2, 2, 2);
const material = new THREE.MeshBasicMaterial({ color: 0xff0000, wireframe: true });
viewWireBox = new THREE.Mesh(geometry, material);
scene.add(viewWireBox);
// Three.js レンダラー設定
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
window.addEventListener('resize', onWindowResize, false);
renderer.render(scene, camera);
}
// リサイズイベント発生時に実行
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function animate() {
requestAnimationFrame(animate);
// Cannon.jsの物理空間の時間を進める
world.fixedStep();
delta = clock.getDelta();
let rotateAngle = Math.PI / 2 * delta;
inputVelocity.set(0, 0, 0);
// 「leftRot」ボタン押下時処理
if(leftRotButtonOn) {
yawObject.rotation.y += rotateAngle;
}
// 「rightRot」ボタン押下時処理
if(rightRotButtonOn) {
yawObject.rotation.y -= rotateAngle;
}
// 「forward」ボタン押下時処理
if(forwardButtonOn) {
inputVelocity.z = velocityFactor * delta * 50;
}
// 「backward」ボタン押下時処理
if(backwardButtonOn) {
inputVelocity.z = -velocityFactor * delta * 50;
}
// ★カメラ操作の計算・設定 Start ★
// カメラ左回転ボタン押下時
if ( camLeftRotButtonOn ) {
targetRot = targetRot - 2;
// カメラ右回転ボタン押下時
} else if ( camRightRotButtonOn ) {
targetRot = targetRot + 2;
}
// 「camReset」ボタン押下時処理
//
// カメラを操作オブジェクトの背後(真後ろ)に配置したいが、直接計算してカメラのtargetRotを設定する方法がわからないので
// 一旦、操作オブジェクト&カメラのベクトル&クォータニオンによりカメラ位置計算後に、
// クォータニオン → 弧度法 → 度数法 → 調整いろいろ、と変換してカメラ位置を設定
//
// テキトーな理解でのテキトーコード作成のためこの方法での位置計算が一般的かは疑わしい。。。
if ( camResetButtonOn ) {
let cameraEuler = new THREE.Euler();
let cameraQuaternion = new THREE.Quaternion();
cameraEuler.y = viewWireBox.rotation.y;
cameraEuler.order = 'XYZ';
cameraQuaternion.setFromEuler(cameraEuler);
// カメラ設定
// カメラの位置をベクトルとクォータニオンで設定
// Z軸方向のベクトルを作成(進行方向のベクトルを作成)
let directionVec = new THREE.Vector3( 0, 0, 1 );
// Z軸方向のベクトル(進行方向のベクトル)にカメラの角度の元となるクォータニオンを適応
directionVec.applyQuaternion(cameraQuaternion);
// カメラの距離をベクトル的に掛け算
directionVec.multiplyScalar(cameraDistance);
// .negate()関数を使用してカメラの位置が操作Boxの背後となるように設定
//let cameraDirection = directionVec.clone();
let cameraDirection = directionVec.clone().negate();
// 操作Boxの位置をベクトルで取得
let viewWireBoxPosition = new THREE.Vector3( viewWireBox.position.x, viewWireBox.position.y, viewWireBox.position.z );
// カメラの高さを表すベクトルを取得
let camHeightVec = new THREE.Vector3( 0, cameraHeight, 0 );
// 操作Boxの位置ベクトル + カメラの(角度を含む)位置ベクトル + カメラの高さベクトル
// → 現在のカメラの位置ベクトルを演算
const cameraPosition = cameraDirection.add(viewWireBoxPosition).add(camHeightVec);
// カメラの位置ベクトルをカメラに設定
camera.position.copy(cameraPosition);
// カメラが操作Boxの方を見るように設定
camera.lookAt(new THREE.Vector3( viewWireBox.position.x, viewWireBox.position.y, viewWireBox.position.z ));
let temp = new THREE.Euler().setFromQuaternion( cameraQuaternion, 'XYZ' );
targetRot = THREE.MathUtils.radToDeg(temp.y)
// viewWireBoxのクォータニオンの値(viewWireBox.quaternion.y)によりカメラの回転設定を調整
// quaternion.y がマイナス値の場合は、quaternion.y が -0.7より大きい場合、-0.7以下の場合で処理を分ける
// quaternion.y が0以上の場合は、quaternion.y が 0.7より小さい場合、0.7以上の場合で処理を分ける
// ↑以上、以下、より小さい、より大きい、の境界線の設定基準はあいまい。。。(自分がよく理解できていないため)
// targetRot = targetRot + 180 の値設定によるカメラ方向が↑上記の場合分けによる状態で異なるためこの処理を行っている
// なぜこのような状態になるのかはほぼ理解できず、とりあえず判定できる状況にあわせていろいろ設定したもの
// quaternion.y がマイナス値の場合(操作オブジェクトが1、3、5、、、回転目はマイナス値となる、たぶん。。。)
if(0 > viewWireBox.quaternion.y) {
// quaternion.y が -0.7より大きい場合
if(-0.7 < viewWireBox.quaternion.y) {
targetRot = targetRot + 180;
// quaternion.y が -0.7以下の場合
} else {
targetRot = 360 - targetRot;
//targetRot = - targetRot;
}
// quaternion.y が0以上の場合(操作オブジェクトが2、4、6、、、回転目はマイナス値となる、たぶん。。。)
} else {
// quaternion.y が 0.7より小さい場合
if(0.7 > viewWireBox.quaternion.y) {
targetRot = targetRot + 180;
// quaternion.y が 0.7以上の場合
} else {
targetRot = 360 - targetRot;
//targetRot = - targetRot;
}
}
// コメントアウト中
// コード中に操作オブジェクトやカメラのパラメータ確認用に使用conslole
//let quaternioValToSee = viewWireBox.quaternion;
//console.log("quaternioValToSee x: " + quaternioValToSee.x + " y: " + quaternioValToSee.y + " z: " + quaternioValToSee.z + " w: " +quaternioValToSee.w);
//console.log("viewWireBox.rotation.y: " + viewWireBox.rotation.y);
//console.log("temp.y: " + temp.y);
//console.log("targetRot: " + targetRot);
}
//rot += 0.5; // 毎フレーム角度を0.5度ずつ足していく
// イージングの公式を用いて滑らかにする
// 値 += (目標値 - 現在の値) * 減速値
rot += (targetRot - rot) * 0.05;
// 角度に応じてカメラの位置を設定
camera.position.x = viewWireBox.position.x + cameraDistance * Math.sin(rot * Math.PI / 180);
camera.position.z = viewWireBox.position.z + cameraDistance * Math.cos(rot * Math.PI / 180);
// 操作対象3Dオブジェクトを見つめる
camera.lookAt(new THREE.Vector3( viewWireBox.position.x, viewWireBox.position.y, viewWireBox.position.z ));
// ★カメラ操作の計算・設定 End ★
// Convert velocity to world coordinates
// 自分の理解では、
// ボタン操作による、操作オブジェクトのy軸回転情報、進行方向ベクトル、のプラマイ(+・-)計算後
// 操作オブジェクトのy軸回転情報をクォータニオンに変換して
// そのクォータニオンを進行方向ベクトルへ適応させ、操作オブジェクトの進行方向&速度を表すベクトルを作成
//euler.x = pitchObject.rotation.x;
euler.y = yawObject.rotation.y;
euler.order = 'XYZ';
quaternion.setFromEuler(euler);
inputVelocity.applyQuaternion(quaternion)
// 上記で作成した操作オブジェクトの進行方向&速度を表すベクトルを操作オブジェクトへ適応
phyWireBox.velocity.x += inputVelocity.x
phyWireBox.velocity.z += inputVelocity.z
// Cannon.jsの物理空間の演算によるSphereの位置・回転をThree.jsによる見た目に反映
viewSphere.position.copy(phySphere.position);
viewSphere.quaternion.copy(phySphere.quaternion);
// Cannon.jsの物理空間の演算によるBoxの位置・回転をThree.jsによる見た目に反映
viewBox.position.copy(phyBox.position);
viewBox.quaternion.copy(phyBox.quaternion);
// Cannon.jsの物理空間の演算によるワイヤーフレームBoxの位置・回転をThree.jsによる見た目に反映
viewWireBox.position.copy(phyWireBox.position);
//viewWireBox.quaternion.copy(phyWireBox.quaternion);
viewWireBox.quaternion.copy(quaternion);
// Three.js レンダリング
renderer.render(scene, camera);
}
function setupEventHandlers(){
//リサイズイベント発生時に実行
window.addEventListener('resize', onWindowResize, false);
//★★★★★★★ ボタン処理スタート ★★★★★★★
//★★★★★★★ 操作対象の3Dオブジェクト操作ボタン処理スタート ★★★★★★★
//★★「forward」ボタン処理★★
//カーソルが「forward」ボタン上にあるとき
forwardButton.addEventListener('pointerenter', () => {
}, false);
//「forward」ボタンが押されたとき
forwardButton.addEventListener('pointerdown', () => {
forwardButtonOn = true;
}, false);
//「forward」ボタンが離されたとき
forwardButton.addEventListener('pointerup', () => {
forwardButtonOn = false;
}, false);
//カーソルが「forward」ボタン上から離れたとき
forwardButton.addEventListener('pointerleave', () => {
forwardButtonOn = false;
}, false);
//★★「leftRot」ボタン処理★★
//「leftRot」ボタンが押されたとき
leftRotButton.addEventListener('pointerdown', () => {
leftRotButtonOn = true;
}, false);
//「leftRot」ボタンが離されたとき
leftRotButton.addEventListener('pointerup', () => {
leftRotButtonOn = false;
}, false);
//カーソルが「leftRot」ボタン上から離れたとき
leftRotButton.addEventListener('pointerleave', () => {
leftRotButtonOn = false;
}, false);
//★★「rightRot」ボタン処理★★
//「rightRot」ボタンが押されたとき
rightRotButton.addEventListener('pointerdown', () => {
rightRotButtonOn = true;
}, false);
//「rightRot」ボタンが離されたとき
rightRotButton.addEventListener('pointerup', () => {
rightRotButtonOn = false;
}, false);
//カーソルが「rightRot」ボタン上から離れたとき
rightRotButton.addEventListener('pointerleave', () => {
rightRotButtonOn = false;
}, false);
//★★「backward」ボタン処理★★
//「backward」ボタンが押されたとき
backwardButton.addEventListener('pointerdown', () => {
backwardButtonOn = true;
}, false);
//「backward」ボタンが離されたとき
backwardButton.addEventListener('pointerup', () => {
backwardButtonOn = false;
}, false);
//カーソルが「backward」ボタン上から離れたとき
backwardButton.addEventListener('pointerleave', () => {
backwardButtonOn = false;
}, false);
//★★★★★★★ 操作対象の3Dオブジェクト操作ボタン処理エンド ★★★★★★★
//★★★★★★★ カメラ操作ボタン処理スタート ★★★★★★★
//★★「camLeftRot」ボタン処理★★
//「camLeftRot」ボタンが押されたとき
camLeftRotButton.addEventListener('pointerdown', () => {
camLeftRotButtonOn = true;
}, false);
//「camLeftRot」ボタンが離されたとき
camLeftRotButton.addEventListener('pointerup', () => {
camLeftRotButtonOn = false;
}, false);
//カーソルが「camLeftRot」ボタン上から離れたとき
camLeftRotButton.addEventListener('pointerleave', () => {
camLeftRotButtonOn = false;
}, false);
//★★「camRightRot」ボタン処理★★
//「camRightRot」ボタンが押されたとき
camRightRotButton.addEventListener('pointerdown', () => {
camRightRotButtonOn = true;
}, false);
//「camRightRot」ボタンが離されたとき
camRightRotButton.addEventListener('pointerup', () => {
camRightRotButtonOn = false;
}, false);
//カーソルが「camRightRot」ボタン上から離れたとき
camRightRotButton.addEventListener('pointerleave', () => {
camRightRotButtonOn = false;
}, false);
//★★「camHeight」ボタン処理★★
//「camHeight」ボタンが押されたとき
camHeightButton.addEventListener('pointerdown', () => {
camera.position.y = camera.position.y + CAMERA_HEIGHT_CALC_UNIT;
if ( camera.position.y > MAX_CAMERA_HEIGHT ) {
camera.position.y = MIM_CAMERA_HEIGHT;
}
}, false);
//「camHeight」ボタンが離されたとき
camHeightButton.addEventListener('pointerup', () => {
}, false);
//カーソルが「camHeight」ボタン上から離れたとき
camHeightButton.addEventListener('pointerleave', () => {
}, false);
//★★「camDist」ボタン処理★★
//「camDist」ボタンが押されたとき
camDistButton.addEventListener('pointerdown', () => {
cameraDistance = cameraDistance - CAMERA_DIST_CALC_UNIT;
if ( cameraDistance < MIM_CAMERA_DIST ){
cameraDistance = MAX_CAMERA_DIST
}
}, false);
//「camDist」ボタンが離されたとき
camDistButton.addEventListener('pointerup', () => {
}, false);
//カーソルが「camDist」ボタン上から離れたとき
camDistButton.addEventListener('pointerleave', () => {
}, false);
//★★「camReset」ボタン処理★★
//「camReset」ボタンが押されたとき
camResetButton.addEventListener('pointerdown', () => {
camResetButtonOn = true;
//targetRot = 0;
camera.position.y = DEFFAULT_CAMERA_HEIGHT;
cameraDistance = DEFFAULT_CAMERA_DIST;
}, false);
//「camReset」ボタンが離されたとき
camResetButton.addEventListener('pointerup', () => {
camResetButtonOn = false;
}, false);
//カーソルが「camReset」ボタン上から離れたとき
camResetButton.addEventListener('pointerleave', () => {
camResetButtonOn = false;
}, false);
//★★★★★★★ カメラ操作ボタン処理エンド ★★★★★★★
//★★★★★★★ ボタン処理エンド ★★★★★★★
}
設定のためのいろいろなパラメータの記録をとっていたもの(手書き)
失くしてしまいそうなのでいちおう写真で保管です。 後でまた見返すことがあるときにもう一度調べたくないw。
ここで調べた操作オブジェクト viewWireBox のクォータニオンの y の値をもとに、コードの
if(0 > viewWireBox.quaternion.y) {….
ではじまる分岐で、
(カメラ位置計算後のクォータニオン → 弧度法 → 度数法 と変換されてきた)TargetRotの値にさらに調整をいろいろ行っています。 とりあえず動くように調整しましたが、なぜそのような調整の計算が必要か、自分にはその原理はまったくわからずw。
(最初の状態の位置と見た目で)操作オブジェクトが、自分自身より画面奥側方向向いているときと、自分自身より画面手前側向いているときで、変換してきたTargetRotの必要とされる値が全くことなるのはなぜなんじゃぜ?
クォータニオンの y と w を記録
temp.y とTargetRot を記録
Vector(DirectionVec)の z と x を記録
最終成果物までの過程となったコードたち
Three.js r146 + cannon-es v0.20.0 Test04
まずこのコードをフォーク(コピペ)するところから作業開始しました。
CodePen Home Three.js r146 + cannon-es v0.20.0 Test07 Camera Rotation Test
フォークしたTest04のコードに、この前回作成したTest07で使用している「カメラを操作オブジェクトの後ろに配置するコード」を移植して、下のTest08を作成しました。
Three.js r146 + cannon-es v0.20.0 Test08
カメラの回転を基本度数法で計算して、カメラリセットのときだけ操作オブジェクトやカメラのクォータニオンから→弧度法→度数法と変換してカメラを回転させる、という試みを最初に行ってみたコードです。 カメラリセット時には、カメラが操作オブジェクトの真後ろに配置されて、操作オブジェクトの方を見るような機能を目指している途中です。
酔っ払いながら作業したせいか、実際のカメラの設置位置が想定の真逆(度数法で180度逆)ですがw、なにかできそうかもと感じて、作業を続けることにしたコードです。 ここより先は酔っ払いながらは無理そうだったのでw、つづきは別の日、以下のコードでの作業としました。
コード自体は、作りかけ、考えかけ、メモなんかのコメントアウト残りまくりのぐちゃぐちゃ気味になってます。
Three.js r146 + cannon-es v0.20.0 Test09
Test08のコードから、さらにあれやこれや試してなんとか目標としていたモンハンカメラっぽい動きを作成したものです。 クォータニオンやベクトルの理解がテキトーなので、実際に値を入れ替えてのあれやこれや試して、意味ほとんどわかってないけどとりあえず目標とした動きになった、といった内容のコードです。 このコードにコメントを追加して、少し整理整頓したものがこのnoteエントリで最終成果物として載せているTest10です。
コード自体は、作りかけ、考えかけ、メモなんかのコメントアウト残りまくりのぐちゃぐちゃ気味になってます、その2です。
次回?
この記事が気に入ったらサポートをしてみませんか?