Source: myAmmo2020.js

/**
 * @fileOverview Ammo のラッパー。
 * @author K.Miyawaki
 */

var mylib2020 = mylib2020 || {};

/**
 * Ammo 物理エンジンへの物体の登録/削除などを行い、物理計算を実行する。
 */
mylib2020.AmmoManager = class {
    /**
     * @param {THREE.Scene} scene Three.js のシーン。
     * @param {THREE.Vector3} gravity 重力の方向。 
     * @param {number} subStep 物理計算の分割数。1 ステップの経過時間をさらに分割して計算したい場合に利用する。 
     */
    constructor(scene, gravity = { x: 0, y: -9.8, z: 0 }, subStep = 1) {
        this.scene = scene;
        [this.world, this.dispatcher, this.overlappingPairCache, this.solver, this.collisionConfiguration]
            = mylib2020.initAmmo(gravity);
        this.targetObjects = new Set();
        this.subStep = subStep;
        this.collisionBuilder = new mylib2020.AmmoCollisionBuilder();
        this.rigidBodyPose = new mylib2020.AmmoRigidBodyPose();
        this.transform = new Ammo.btTransform();
    }

    applyPhysics(threeObject) {
        if (threeObject.userData.rigidBody) {
            mylib2020.applyAmmo(threeObject, threeObject.userData.rigidBody, this.transform);
        }
    }

    /**
     * 物体を弾き飛ばす、ジャンプさせるなど瞬間的な力を加える。
     * @param {THREE.Object3D} threeObject userData.rigidBody に Ammo.btRigidBody を持つ物体。
     * @param {THREE.Vector3} impulse 力の方向。 
     */
    addImpulse(threeObject, impulse) {
        if (threeObject.userData.rigidBody) {
            mylib2020.addImpulse(threeObject.userData.rigidBody, impulse);
        }
    }

    /**
     * 物体を指定した方向に押す。
     * @param {THREE.Object3D} threeObject userData.rigidBody に Ammo.btRigidBody を持つ物体。
     * @param {THREE.Vector3} force 力の方向。 
     */
    addForce(threeObject, force) {
        if (threeObject.userData.rigidBody) {
            mylib2020.addForce(threeObject.userData.rigidBody, force);
        }
    }

    /**
     * 物体をシーンと物理エンジンに登録する。CG も表示されるし、物理計算の影響も受けるようになる。
     * @param {THREE.Object3D} threeObject userData.rigidBody に Ammo.btRigidBody を持つ物体。
     */
    registerObject(threeObject) {
        if (this.targetObjects.has(threeObject)) {
            return false;
        }
        this.scene.add(threeObject);
        this.targetObjects.add(threeObject);
        if (threeObject.userData.rigidBody) {
            this.world.addRigidBody(threeObject.userData.rigidBody);
            threeObject.userData.collision = new mylib2020.AmmoCollisionManager();
        }
    }

    /**
     * 物体が物理エンジンの管理下にあるかどうか。
     * @param {boolean} threeObject 検査対象。
     */
    has(threeObject) {
        return this.targetObjects.has(threeObject);
    }

    /**
     * 物理計算を 1 ステップ行う。<br/>
     * userData.movable が true かつ、 userData.linearVelocity もしくは userData.angularVelocity に速度を持つ物体は移動した上で物理エンジンの影響を受ける。
     * @param {number} delta 経過時間。
     */
    update(delta) {
        for (let k of this.targetObjects) {
            if (!k.userData.movable) {
                continue;
            }
            if (k.userData.linearVelocity) {
                k.translateOnAxis(mylib2020.FORWARD, k.userData.linearVelocity * delta);
            }
            if (k.userData.angularVelocity) {
                k.rotateY(k.userData.angularVelocity * delta);
            }
            this.rigidBodyPose.synch(k);
        }
        this.world.stepSimulation(delta, this.subStep);
        for (let k of this.targetObjects) {
            this.applyPhysics(k);
        }
        this.updateCollision();
    }

    updateCollision() {
        const numManifolds = this.dispatcher.getNumManifolds();
        for (let i = 0; i < numManifolds; i++) {
            const manifold = this.dispatcher.getManifoldByIndexInternal(i); // btPersistentManifold
            const obA = Ammo.btRigidBody.prototype.upcast(manifold.getBody0()); // btCollisionObject 
            const obB = Ammo.btRigidBody.prototype.upcast(manifold.getBody1()); // btCollisionObject
            if (!obA.threeObject || !obB.threeObject) {
                console.log("updateCollision: found unregistered objects");
                continue;
            }
            const numContacts = manifold.getNumContacts(); // オブジェクト間の衝突点数
            for (let j = 0; j < numContacts; j++) {
                const pt = manifold.getContactPoint(j);//btManifoldPoint
                if (pt.getDistance() <= 0.0) {
                    const ptA = pt.getPositionWorldOnA(); // btVector3
                    const ptB = pt.getPositionWorldOnB(); // btVector3
                }
            }
            if (numContacts > 0) {
                obA.threeObject.userData.collision.addNewCollision(obB.threeObject);
                obB.threeObject.userData.collision.addNewCollision(obA.threeObject);
            }
        }
        for (let obj of this.targetObjects) {
            obj.userData.collision.update();
        }
    }

    removePhysics(threeObject) {
        if (threeObject.userData.collision) {
            delete threeObject.userData.collision;
        }
        if (threeObject.userData.rigidBody) {
            this.world.removeRigidBody(threeObject.userData.rigidBody);
            mylib2020.removeAmmoRigidBody(threeObject);
            mylib2020.removeAmmoShapeRecursive(threeObject);
        }
    }

    /**
     * 物理エンジンと 3D シーンから物体を削除する。
     * @param {THREE.Object3D} threeObject 
     */
    remove(threeObject) {
        this.removePhysics(threeObject);
        this.targetObjects.delete(threeObject);
        this.scene.remove(threeObject);
    }

    /**
     * 全ての物体をシーンと物理エンジンから削除する。
     */
    clear() {
        for (let obj of this.targetObjects) {
            this.removePhysicsRecursive(obj);
            this.scene.remove(threeObject);
        }
        this.targetObjects.clear();
    }

    destroy() {
        this.clear();
        this.collisionBuilder.destroy();
        this.rigidBodyPose.destroy();
        Ammo.destroy(this.transform);
        Ammo.destroy(this.world);
        Ammo.destroy(this.dispatcher);
        Ammo.destroy(this.overlappingPairCache);
        Ammo.destroy(this.solver);
        Ammo.destroy(this.collisionConfiguration);
    }
}

/**
 * Ammo の衝突検出形状や剛体(形状が変化しない物体)を生成する。
 */
mylib2020.AmmoCollisionBuilder = class {
    constructor() {
        this.position = new Ammo.btVector3();
        this.rotation = new Ammo.btQuaternion();
        this.transform = new Ammo.btTransform();
        this.localInertia = new Ammo.btVector3();
        this.size = new Ammo.btVector3();
        this.scale = new Ammo.btVector3();
        this.factor = new Ammo.btVector3();
        this.vertex = new Ammo.btVector3();
        this.minLength = 0.01;
        this.collisionShapeMaterial = new THREE.MeshBasicMaterial({ color: 0x00ff00, wireframe: true });
    }

    /**
     * 基本的な形状を geometry に持つ threeObject に対して同じ大きさの衝突検出範囲を生成し、threeObject.userData.collisionShape に代入する。<br/>
     * さらに、その形状に基づく剛体を生成して threeObject.userData.rigidBody に代入する。<br/>
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * 対応する形状は次の通り。 threeObject.geometry が下記のいずれかでなければならない。
     * <ul>
     *   <li>THREE.PlaneGeometry</li>
     *   <li>THREE.BoxGeometry</li>
     *   <li>THREE.SphereGeometry</li>
     *   <li>THREE.CylinderGeometry<br/>
     *     <b>※真円かつ、底面と上面が同じ大きさの円柱しか対応していない。つまり、 x, z の scale が等しい必要がある。</b></li>
     *   <li>THREE.ConeGeometry<br/>
     *     <b>※底面が真円の円錐しか対応していない。つまり、 x, z の scale が等しい必要がある。</b></li>
     * </ul>
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody} 生成された剛体。失敗した場合は null が返る。
     */
    addPrimitiveRigidBody(threeObject, params = null) {
        if (this.addPrimitiveShape(threeObject) == null) {
            return null;
        }
        return this.addRigidBody(threeObject, params);
    }
    /**
     * threeObject を包むような直方体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * さらに、その形状に基づく剛体を生成して threeObject.userData.rigidBody に代入する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody} 生成された剛体。失敗した場合は null が返る。
     */
    addBoundingBoxRigidBody(threeObject, params = null) {
        this.addBoundingBoxShape(threeObject, params);
        return this.addRigidBody(threeObject, params);
    }

    /**
     * threeObject を包むような球体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * さらに、その形状に基づく剛体を生成して threeObject.userData.rigidBody に代入する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody} 生成された剛体。失敗した場合は null が返る。
     */
    addBoundingSphereRigidBody(threeObject, params = null) {
        this.addBoundingSphereShape(threeObject, params);
        return this.addRigidBody(threeObject, params);
    }

    /**
     * threeObject を包むような円柱の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * <b>※真円かつ、底面と上面が同じ大きさの円柱しか生成しない。</b><br/>
     * さらに、その形状に基づく剛体を生成して threeObject.userData.rigidBody に代入する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>axis - string 円柱の中心軸 "x", "y", "z" のいずれか (デフォルト: y)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody}
     */
    addBoundingCylinderRigidBody(threeObject, params = null) {
        this.addBoundingCylinderShape(threeObject, params);
        return this.addRigidBody(threeObject, params);
    }

    /**
     * threeObject とその子に対して衝突検出範囲を設定し、それらを結合ものを threeObject.userData.collisionShape に代入する。<br/>
     * さらに、その形状に基づく剛体を生成して threeObject.userData.rigidBody に代入する。<br/>
     * 子が userData.collisionShape を既に持っていればそれを衝突検出範囲として使う。
     * 無い場合、子に対し addPrimitiveShape が使用され、失敗した場合は範囲設定しない。また、子が geometry を持っていない場合も衝突検出範囲を生成しない。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody} 生成された剛体。失敗した場合は null が返る。
     */
    addCompoundRigidBody(threeObject, params = null) {
        this.addCompoundShape(threeObject);
        return this.addRigidBody(threeObject, params);
    }

    /**
     * Three.js の基本的な形状と同じ大きさの衝突検出範囲を生成し、threeObject.userData.collisionShape に代入する。<br/>
     * 対応する形状は次の通り。 threeObject.geometry が下記のいずれかでなければならない。
     * <ul>
     *   <li>THREE.PlaneGeometry</li>
     *   <li>THREE.BoxGeometry</li>
     *   <li>THREE.SphereGeometry</li>
     *   <li>THREE.CylinderGeometry<br/>
     *     <b>※真円かつ、底面と上面が同じ大きさの円柱しか対応していない。つまり、 x, z の scale が等しい必要がある。</b></li>
     *   <li>THREE.ConeGeometry<br/>
     *     <b>※底面が真円の円錐しか対応していない。つまり、 x, z の scale が等しい必要がある。</b></li>
     * </ul>
     * @author (Set the text for this tag by adding docthis.authorName to your settings file.)
     * @param {THREE.Mesh} threeObject
     * @returns {any} 成功した場合、threeObject.geometry に対応する生成された衝突検出範囲の形状が返る。次のいずれかである。
     * <ul>
     *   <li>THREE.PlaneGeometry -> Ammo.btConvexHullShape</li>
     *   <li>THREE.BoxGeometry -> Ammo.btBoxShape</li>
     *   <li>THREE.SphereGeometry -> Ammo.btMultiSphereShape</li>
     *   <li>THREE.CylinderGeometry -> Ammo.btCylinderShape</li>
     *   <li>THREE.ConeGeometry -> Ammo.btConeShape</li>
     * </ul>
     * 失敗した場合、 null が返る。
     */
    addPrimitiveShape(threeObject) {
        const shape = this.makePrimitiveShape(threeObject);
        if (shape) {
            mylib2020.addAmmoShape(threeObject, shape);
        }
        return shape;
    }

    /**
     * 頂点を包む凸包の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。
     * @param {THREE.Mesh} threeObject Three.js の物体。
     * @param {Array<THREE.Vector3>} vertices 物体の頂点配列。
     * @param {THREE.Vector3} scale 検出範囲の拡大縮小率。
     * @returns {Ammo.btConvexHullShape}
     */
    addConvexHullShape(threeObject, vertices, scale) {
        const shape = this.makeConvexHullShape(vertices, scale);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject と同じ大きさの平面の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * @param {THREE.Mesh} threeObject Three.js の物体。THREE.PlaneGeometry を持つ必要がある。
     * @returns {Ammo.btConvexHullShape}
     */
    addPlaneShape(threeObject) {
        const shape = this.makePlaneShape(threeObject);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject と同じ大きさの直方体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * @param {THREE.Mesh} threeObject Three.js の物体。THREE.BoxGeometry を持つ必要がある。
     * @returns {Ammo.btBoxShape}
     */
    addBoxShape(threeObject) {
        const shape = this.makeBoxShape(threeObject);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject と同じ大きさの球体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * @param {THREE.Mesh} threeObject Three.js の物体。THREE.SphereGeometry を持つ必要がある。
     * @returns {Ammo.btMultiSphereShape}
     */
    addSphereShape(threeObject) {
        const shape = this.makeSphereShape(threeObject);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject と同じ大きさの円柱の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * <b>※真円かつ、底面と上面が同じ大きさの円柱しか対応していない。つまり、 x, z の scale が等しい必要がある。</b>
     * @param {THREE.Mesh} threeObject Three.js の物体。THREE.CylinderGeometry を持つ必要がある。
     * @returns {Ammo.btCylinderShape}
     */
    addCylinderShape(threeObject) {
        const shape = this.makeCylinderShape(threeObject);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject と同じ大きさの円錐の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * <b>※底面が真円の円錐しか対応していない。つまり、 x, z の scale が等しい必要がある。</b>
     * @param {THREE.Mesh} threeObject Three.js の物体。THREE.ConeGeometry を持つ必要がある。
     * @returns {Ammo.btConeShape}
     */
    addConeShape(threeObject) {
        const shape = this.makeConeShape(threeObject);
        mylib2020.addAmmoShape(threeObject, shape);
        return shape;
    }

    /**
     * threeObject を包むような円柱の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。<br/>
     * <b>※真円かつ、底面と上面が同じ大きさの円柱しか生成しない。</b>
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>axis - string 円柱の中心軸 "x", "y", "z" のいずれか (デフォルト: y)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     * </ul>
     * @returns {Ammo.btCompoundShape}
     */
    addBoundingCylinderShape(threeObject, params = null) {
        params = params || {};
        const showCollision = ('showCollision' in params) ? params.showCollision : false;
        const axis = ('axis' in params) ? params.axis.toLowerCase() : "y";
        if (axis != "x" && axis != "y" && axis != "z") {
            axis = "y";
        }
        const scale = ('scale' in params) ? params.scale : new THREE.Vector3(1, 1, 1);
        const offset = ('offset' in params) ? params.offset : new THREE.Vector3(0, 0, 0);

        let radius = 0;
        let height = 0;
        let rotation = new THREE.Vector3();
        const bbox = this.calcBoundingBox(threeObject, scale, offset);
        if (axis == "x") {
            height = bbox.size.x;
            radius = Math.max(bbox.size.y, bbox.size.z) / 2;
            rotation.z = THREE.Math.degToRad(90);
        } else if (axis == "y") {
            height = bbox.size.y;
            radius = Math.max(bbox.size.x, bbox.size.z) / 2;
        } else if (axis == "z") {
            height = bbox.size.z;
            radius = Math.max(bbox.size.x, bbox.size.y) / 2;
            rotation.x = THREE.Math.degToRad(90);
        }

        const geom = new THREE.CylinderGeometry(radius, radius, height, 8);
        const childMesh = new THREE.Mesh(geom, this.collisionShapeMaterial.clone());
        if (showCollision == false) {
            childMesh.material.transparent = true;
            childMesh.material.opacity = 0.0;
        }
        childMesh.name = threeObject.name + "_BCYLINDER";
        childMesh.rotation.set(rotation.x, rotation.y, rotation.z);
        childMesh.position.set(bbox.center.x, bbox.center.y, bbox.center.z);
        threeObject.add(childMesh);
        return this.addCompoundShape(threeObject);
    }

    /**
     * threeObject を包むような球体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     * </ul>
     * @returns {Ammo.btCompoundShape}
     */
    addBoundingSphereShape(threeObject, params = null) {
        params = params || {};
        const showCollision = ('showCollision' in params) ? params.showCollision : false;
        const scale = ('scale' in params) ? params.scale : new THREE.Vector3(1, 1, 1);
        const offset = ('offset' in params) ? params.offset : new THREE.Vector3(0, 0, 0);

        const bbox = this.calcBoundingBox(threeObject, scale, offset);
        const geom = new THREE.SphereGeometry(0.5, 8, 8);
        const childMesh = new THREE.Mesh(geom, this.collisionShapeMaterial.clone());
        if (showCollision == false) {
            childMesh.material.transparent = true;
            childMesh.material.opacity = 0.0;
        }
        childMesh.scale.set(bbox.size.x, bbox.size.y, bbox.size.z);
        childMesh.name = threeObject.name + "_BSPHERE";
        childMesh.position.set(bbox.center.x, bbox.center.y, bbox.center.z);
        threeObject.add(childMesh);
        return this.addCompoundShape(threeObject);
    }

    /**
     * threeObject を包むような直方体の衝突検出範囲を生成し threeObject.userData.collisionShape に代入する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>showCollision - boolean 衝突検知範囲を可視化するか (デフォルト: false)</li>
     *   <li>scale - THREE.Vector3 衝突検知範囲の拡大縮小率 (デフォルト: x, y, z 全て 1.0)</li>
     *   <li>offset - THREE.Vector3 衝突検知範囲の中心からのオフセット (デフォルト: x, y, z 全て 0.0)</li>
     * </ul>
     * @returns {Ammo.btCompoundShape}
     */
    addBoundingBoxShape(threeObject, params = null) {
        params = params || {};
        const showCollision = ('showCollision' in params) ? params.showCollision : false;
        const scale = ('scale' in params) ? params.scale : new THREE.Vector3(1, 1, 1);
        const offset = ('offset' in params) ? params.offset : new THREE.Vector3(0, 0, 0);

        const bbox = this.calcBoundingBox(threeObject, scale, offset);
        const geom = new THREE.BoxGeometry(bbox.size.x, bbox.size.y, bbox.size.z);
        const childMesh = new THREE.Mesh(geom, this.collisionShapeMaterial.clone());
        if (showCollision == false) {
            childMesh.material.transparent = true;
            childMesh.material.opacity = 0.0;
        }
        childMesh.name = threeObject.name + "_BBOX";
        childMesh.position.set(bbox.center.x, bbox.center.y, bbox.center.z);
        threeObject.add(childMesh);
        return this.addCompoundShape(threeObject);
    }

    /**
     * 子を持つ threeObject に対し全ての子が持つ衝突検出範囲を結合して threeObject.userData.collisionShape に代入する。<br/>
     * 子が userData.collisionShape を既に持っていればそれを衝突検出範囲として使う。
     * 無い場合、子に対し addPrimitiveShape が使用され、失敗した場合は範囲設定はされない。
     * 子が geometry を持っていない場合は衝突検出範囲を生成しない。
     * @param {THREE.Object3D} threeObject 子を持つ Three.js の物体。
     * @returns {Ammo.btCompoundShape}
     */
    addCompoundShape(threeObject) {
        let shape = new Ammo.btCompoundShape();
        if (threeObject.geometry && !threeObject.userData.collisionShape) {
            this.addPrimitiveShape(threeObject);
        }
        if (threeObject.userData.collisionShape) {
            threeObject.userData.collisionShapeOrg = threeObject.userData.collisionShape;
            this.transform.setIdentity();
            shape.addChildShape(this.transform, threeObject.userData.collisionShapeOrg);
        }
        for (let child of threeObject.children) {
            const childShape = this.addCompoundShape(child);
            if (childShape) {
                this.position.setValue(child.position.x, child.position.y, child.position.z);
                this.rotation.setValue(child.quaternion.x, child.quaternion.y, child.quaternion.z, child.quaternion.w);
                this.transform.setIdentity();
                this.transform.setOrigin(this.position);
                this.transform.setRotation(this.rotation);
                shape.addChildShape(this.transform, childShape);
            }
        }
        if (shape.getNumChildShapes() > 0) {
            threeObject.userData.collisionShape = shape;
        } else {
            Ammo.destroy(shape);
            shape = null;
        }
        return shape;
    }

    /**
     * threeObject.userData.collisionShape に衝突検出範囲を持つ threeObject に対し剛体を生成して threeObject.userData.rigidBody に代入する。<br/>
     * threeObject が threeObject.userData.collisionShape に衝突検出範囲を持っていなければ何もしない。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {Object} params 次のようなキーでパラメータ指定する。null のとき、デフォルト値が使用される。
     * <ul>
     *   <li>movable - boolean ユーザが動かす予定がある物体は true にする。(デフォルト: false)</li>
     *   <li>mass - number 重さ。単位は Kg (デフォルト: 1)</li>
     *   <li>friction - number 面で接触する物体に対する摩擦。(デフォルト: 0.5)</li>
     *   <li>rollingFriction - number 線や点で接触する、転がる物体に対する摩擦。(デフォルト: 0.1)</li>
     *   <li>restitution - number 反発の程度。(デフォルト: 0)</li>
     *   <li>freezeRotationX - boolean X 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>freezeRotationY - boolean Y 軸中心の回転をさせないかどうか。(デフォルト: false)</li>
     *   <li>linearDamping - number 移動に対する空気抵抗。大きいほど減速しやすい。(デフォルト: 0)</li>
     *   <li>angularDamping - number 回転に対する空気抵抗。大きいほど回転が止まりやすい。(デフォルト: 0)</li>
     *   <li>margin - number 衝突検出範囲に余裕を持たせる場合の距離。(デフォルト: 0.01)</li>
     * </ul>
     * @returns {Ammo.btRigidBody} 生成された剛体。失敗した場合は null が返る。
     */
    addRigidBody(threeObject, params = null) {
        if (threeObject.userData.collisionShape) {
            const rigidBody = this.makeRigidBody(threeObject.position, threeObject.quaternion, threeObject.userData.collisionShape, params);
            mylib2020.addAmmoRigidBody(threeObject, rigidBody);
            this.checkMovable(threeObject, params);
            return rigidBody;
        }
        return null;
    }

    // Shape 作成系
    makePrimitiveShape(threeObject) {
        if (!threeObject.geometry || !threeObject.geometry.type) {
            return null;
        }
        if (threeObject.geometry.type === "PlaneGeometry") {
            return this.makePlaneShape(threeObject);
        } else if (threeObject.geometry.type === "BoxGeometry") {
            return this.makeBoxShape(threeObject);
        } else if (threeObject.geometry.type === "SphereGeometry") {
            return this.makeSphereShape(threeObject);
        } else if (threeObject.geometry.type === "CylinderGeometry") {
            return this.makeCylinderShape(threeObject);
        } else if (threeObject.geometry.type === "ConeGeometry") {
            return this.makeConeShape(threeObject);
        }
        return null;
    }

    makeConvexHullShape(vertices, scale) {
        const shape = new Ammo.btConvexHullShape();
        for (let v of vertices) {
            this.vertex.setValue(v.x, v.y, v.z);
            shape.addPoint(this.vertex);
        }
        this.scale.setValue(scale.x, scale.y, scale.z);
        shape.setLocalScaling(this.scale);
        return shape;
    }

    makePlaneShape(threeObject) {
        const geometryParams = threeObject.geometry.parameters;
        const w2 = geometryParams.width / 2; // X
        const h2 = geometryParams.height / 2; // Y
        const vertices = [
            new THREE.Vector3(-w2, -h2, 0),
            new THREE.Vector3(w2, -h2, 0),
            new THREE.Vector3(w2, h2, 0),
            new THREE.Vector3(-w2, h2, 0)
        ];
        return this.makeConvexHullShape(vertices, threeObject.scale);
    }

    makeBoxShape(threeObject) {
        threeObject.geometry.computeBoundingBox();
        const bbox = threeObject.geometry.boundingBox;
        const x = Math.max(this.minLength, Math.abs(bbox.max.x - bbox.min.x));
        const y = Math.max(this.minLength, Math.abs(bbox.max.y - bbox.min.y));
        const z = Math.max(this.minLength, Math.abs(bbox.max.z - bbox.min.z));
        this.size.setValue(x * 0.5, y * 0.5, z * 0.5);
        this.scale.setValue(threeObject.scale.x, threeObject.scale.y, threeObject.scale.z);
        const shape = new Ammo.btBoxShape(this.size);
        shape.setLocalScaling(this.scale);
        return shape;
    }

    makeSphereShape(threeObject) {
        threeObject.geometry.computeBoundingSphere();
        const sphere = threeObject.geometry.boundingSphere;
        this.position.setValue(0, 0, 0);
        this.scale.setValue(threeObject.scale.x, threeObject.scale.y, threeObject.scale.z);
        const shape = new Ammo.btMultiSphereShape([this.position], [sphere.radius], 1);
        shape.setLocalScaling(this.scale);
        return shape;
    }

    makeCylinderShape(threeObject) {
        threeObject.geometry.computeBoundingBox();
        const bbox = threeObject.geometry.boundingBox;
        const x = Math.max(this.minLength, Math.abs(bbox.max.x - bbox.min.x));
        const y = Math.max(this.minLength, Math.abs(bbox.max.y - bbox.min.y)); // Height
        const z = Math.max(this.minLength, Math.abs(bbox.max.z - bbox.min.z));
        const radius = Math.max(x, z) / 2;
        this.size.setValue(radius, y * 0.5, radius);
        this.scale.setValue(threeObject.scale.x, threeObject.scale.y, threeObject.scale.z);
        const shape = new Ammo.btCylinderShape(this.size);
        shape.setLocalScaling(this.scale);
        return shape;
    }

    makeConeShape(threeObject) {
        const geometryParams = threeObject.geometry.parameters;
        const radius = geometryParams.radius;
        const height = geometryParams.height;
        const shape = new Ammo.btConeShape(radius, height);
        this.scale.setValue(threeObject.scale.x, threeObject.scale.y, threeObject.scale.z);
        shape.setLocalScaling(this.scale);
        return shape;
    }

    // ヘルパー
    checkMovable(threeObject, params = null) {
        params = params || {};
        threeObject.userData.movable = ('movable' in params) ? params.movable : false;
        if (threeObject.userData.movable) {
            threeObject.userData.linearVelocity = 0;
            threeObject.userData.angularVelocity = 0;
        }
    }

    /**
     * threeObject を包むような直方体の大きさとローカル中心座標を計算する。
     * @param {THREE.Object3D} threeObject Three.js の物体。
     * @param {THREE.Vector3} scale 直方体の拡大縮小率。
     * @param {THREE.Vector3} offset 直方体の中心に加算する値。
     * @returns {Object} 
     * <ul>
     *   <li>size - 大きさ {x, y, z}</li>
     *   <li>center - THREE.Vector3</li>
     * </ul>
     */
    calcBoundingBox(threeObject, scale, offset) {
        const bbox = new THREE.Box3();
        const center = new THREE.Vector3();
        bbox.setFromObject(threeObject);
        bbox.getCenter(center);
        threeObject.worldToLocal(center);
        const x = Math.max(this.minLength, Math.abs(bbox.max.x - bbox.min.x)) * scale.x;
        const y = Math.max(this.minLength, Math.abs(bbox.max.y - bbox.min.y)) * scale.y;
        const z = Math.max(this.minLength, Math.abs(bbox.max.z - bbox.min.z)) * scale.z;
        center.add(offset);
        return { size: { x, y, z }, center: center };
    }

    // 剛体生成
    makeRigidBody(pos, quat, shape, params = null) {
        params = params || {};
        const mass = ('mass' in params) ? params.mass : 1;
        const friction = ('friction' in params) ? params.friction : 0.5;
        const rollingFriction = ('rollingFriction' in params) ? params.rollingFriction : 0.0;
        const restitution = ('restitution' in params) ? params.restitution : 0;
        const kinematic = ('kinematic' in params) ? params.kinematic : false;
        const freezeRotationX = ('freezeRotationX' in params) ? params.freezeRotationX : false;
        const freezeRotationY = ('freezeRotationY' in params) ? params.freezeRotationY : false;
        const freezeRotationZ = ('freezeRotationZ' in params) ? params.freezeRotationZ : false;
        const linearDamping = ('linearDamping' in params) ? params.linearDamping : 0;
        const angularDamping = ('angularDamping' in params) ? params.angularDamping : 0;

        const margin = ('margin' in params) ? params.margin : 0.01;
        shape.setMargin(margin);
        this.localInertia.setValue(0, 0, 0);
        shape.calculateLocalInertia(mass, this.localInertia);

        this.position.setValue(pos.x, pos.y, pos.z);
        this.rotation.setValue(quat.x, quat.y, quat.z, quat.w);
        this.transform.setIdentity();
        this.transform.setOrigin(this.position);
        this.transform.setRotation(this.rotation);
        const motionState = new Ammo.btDefaultMotionState(this.transform);
        const rbInfo = new Ammo.btRigidBodyConstructionInfo(mass, motionState, shape, this.localInertia);
        const rigidBody = new Ammo.btRigidBody(rbInfo);
        rigidBody.setRollingFriction(rollingFriction);
        rigidBody.setFriction(friction);
        rigidBody.setRestitution(restitution);
        rigidBody.setDamping(linearDamping, angularDamping);
        if (kinematic) {
            rigidBody.setActivationState(Ammo.DISABLE_DEACTIVATION);
            rigidBody.setCollisionFlags(rigidBody.getCollisionFlags() | Ammo.CF_KINEMATIC_OBJECT);
        }
        if (freezeRotationX || freezeRotationY || freezeRotationZ) {
            this.factor.setValue(1, 1, 1);
            if (freezeRotationX) {
                this.factor.setX(0);
            }
            if (freezeRotationY) {
                this.factor.setY(0);
            }
            if (freezeRotationZ) {
                this.factor.setZ(0);
            }
            rigidBody.setAngularFactor(this.factor);
        }
        Ammo.destroy(rbInfo);
        return rigidBody;
    }

    destroy() {
        Ammo.destroy(this.position);
        Ammo.destroy(this.rotation);
        Ammo.destroy(this.transform);
        Ammo.destroy(this.localInertia);
        Ammo.destroy(this.size);
        Ammo.destroy(this.scale);
        Ammo.destroy(this.factor);
        Ammo.destroy(this.vertex);
    }
}

/**
 * 物体同士の衝突を管理する。
 */
mylib2020.AmmoCollisionManager = class {
    constructor() {
        this.newCollisions = new Map();
        this.all = new Map();
        this.enter = new Map();
        this.stay = new Map();
        this.exit = new Map();
    }

    /**
     * 今回のフレームで接触している全ての物体を得る。for(let object of ...)で個々の物体にアクセス可能。
     * @returns {Iterator} of を使って for 文でアクセスできる。
     * @example 
     * for (let object of target.userData.collision.getAll()) {
     *     // target と接触している個々の物体を参照できる。
     * }
     */
    getAll() {
        return this.all.keys();
    }

    /**
     * 今回のフレームで接触を開始した全ての物体を得る。for(let object of ...)で個々の物体にアクセス可能。
     * @returns {Iterator} of を使って for 文でアクセスできる。
     * @example 
     * for (let object of target.userData.collision.getEnter()) {
     *     // target と接触を開始した個々の物体を参照できる。
     * }
     */
    getEnter() {
        return this.enter.keys();
    }

    /**
     * 今回のフレームで接触を終了した全ての物体を得る。for(let object of ...)で個々の物体にアクセス可能。
     * @returns {Iterator} of を使って for 文でアクセスできる。
     * @example 
     * for (let object of target.userData.collision.getExit()) {
     *     // target と接触を終了した個々の物体を参照できる。
     * }
     */
    getExit() {
        return this.exit.keys();
    }

    /**
     * 前回のフレームから接触を継続している全ての物体を得る。for(let object of ...)で個々の物体にアクセス可能。
     * @returns {Iterator} of を使って for 文でアクセスできる。
     * @example 
     * for (let object of target.userData.collision.getStay()) {
     *     // target と接触を継続している個々の物体を参照できる。
     * }
     */
    getStay() {
        return this.stay.keys();
    }

    collisionData() {
        return { sourceContactPoints: [], targetContactPoints: [] };
    }

    addNewCollision(targetThreeObject) {
        if (!this.newCollisions.has(targetThreeObject)) {
            this.newCollisions.set(targetThreeObject, this.collisionData());
        }
    }

    update() {
        this.enter.clear();
        this.stay.clear();
        for (let k of this.newCollisions.keys()) {
            if (this.all.has(k)) {
                this.stay.set(k, this.newCollisions[k]);
                this.all.delete(k);
            } else {
                this.enter.set(k, this.newCollisions[k]);
            }
        }
        this.exit = this.all;
        this.all = this.newCollisions;
        this.newCollisions = new Map();
    }
}

/**
 * Ammo.btRigidBody の位置、姿勢を設定する。
 */
mylib2020.AmmoRigidBodyPose = class {
    constructor() {
        this.position = new Ammo.btVector3();
        this.quaternion = new Ammo.btQuaternion();
        this.transform = new Ammo.btTransform();
        this.ACTIVE_TAG = 1;
    }

    /**
     * Ammo.btRigidBody の位置、姿勢を設定する。
     * @param {Ammo.btRigidBody} rigidBody 設定対象。
     * @param {THREE.Vector3} position 位置。
     * @param {THREE.Quaternion} quaternion 姿勢。
     */
    set(rigidBody, position, quaternion) {
        this.position.setValue(position.x, position.y, position.z);
        this.quaternion.setValue(quaternion.x, quaternion.y, quaternion.z, quaternion.w);
        this.transform.setIdentity();
        this.transform.setOrigin(this.position);
        this.transform.setRotation(this.quaternion);
        const motionState = new Ammo.btDefaultMotionState(this.transform);
        Ammo.destroy(rigidBody.getMotionState());
        rigidBody.forceActivationState(this.ACTIVE_TAG);
        rigidBody.activate();
        rigidBody.setMotionState(motionState);
    }

    /**
     * threeObject の位置、姿勢を threeObject.userData.rigidBody にある Ammo.btRigidBody に反映させる。
     * @param {THREE.Object3D} threeObject threeObject.userData.rigidBody に Ammo.btRigidBody を持っている THREE.Object3D。
     */
    synch(threeObject) {
        if (threeObject.userData.rigidBody) {
            this.set(threeObject.userData.rigidBody, threeObject.position, threeObject.quaternion);
        }
    }

    destroy() {
        Ammo.destroy(this.position);
        Ammo.destroy(this.quaternion);
        Ammo.destroy(this.transform);
    }
}

mylib2020.initAmmo = function (gravity) {
    const collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
    const dispatcher = new Ammo.btCollisionDispatcher(collisionConfiguration);
    const overlappingPairCache = new Ammo.btDbvtBroadphase();
    const solver = new Ammo.btSequentialImpulseConstraintSolver();
    const world = new Ammo.btDiscreteDynamicsWorld(
        dispatcher,
        overlappingPairCache,
        solver,
        collisionConfiguration
    );
    const gv = new Ammo.btVector3(gravity.x, gravity.y, gravity.z);
    world.setGravity(gv);
    Ammo.destroy(gv);
    return [world, dispatcher, overlappingPairCache, solver, collisionConfiguration];
}

mylib2020.addAmmoRigidBody = function (threeObject, rigidBody) {
    mylib2020.removeAmmoRigidBody(threeObject);
    threeObject.userData.rigidBody = rigidBody;
    rigidBody.threeObject = threeObject;
}

mylib2020.removeAmmoRigidBody = function (threeObject) {
    if (threeObject.userData.rigidBody) {
        if (threeObject.userData.rigidBody.threeObject) {
            delete threeObject.userData.rigidBody.threeObject;
        }
        Ammo.destroy(threeObject.userData.rigidBody.getMotionState());
        Ammo.destroy(threeObject.userData.rigidBody);
        delete threeObject.userData.rigidBody;
    }
}

mylib2020.addAmmoShape = function (threeObject, shape) {
    mylib2020.removeAmmoShape(threeObject);
    threeObject.userData.collisionShape = shape;
}

mylib2020.removeAmmoShapeRecursive = function (threeObject) {
    mylib2020.removeAmmoShape(threeObject);
    for (let child of threeObject.children) {
        mylib2020.removeAmmoShapeRecursive(child);
    }
}

mylib2020.removeAmmoShape = function (threeObject) {
    if (threeObject.userData.collisionShape) {
        Ammo.destroy(threeObject.userData.collisionShape);
        delete threeObject.userData.collisionShape;
    }
    if (threeObject.userData.collisionShapeOrg) {
        Ammo.destroy(threeObject.userData.collisionShapeOrg);
        delete threeObject.userData.collisionShapeOrg;
    }
}

mylib2020.addImpulse = function (rigidBody, impulse = new THREE.Vector3(0, 0, 0)) {
    const imp = new Ammo.btVector3(impulse.x, impulse.y, impulse.z);
    rigidBody.applyCentralImpulse(imp);
    Ammo.destroy(imp);
}

mylib2020.addForce = function (rigidBody, force = new THREE.Vector3(0, 0, 0)) {
    const f = new Ammo.btVector3(force.x, force.y, force.z);
    rigidBody.applyCentralForce(f);
    Ammo.destroy(f);
}

mylib2020.applyAmmo = function (threeObject, rigidBody, btTransformBuffer) {
    rigidBody.getMotionState().getWorldTransform(btTransformBuffer);
    const p = btTransformBuffer.getOrigin();
    const q = btTransformBuffer.getRotation();
    threeObject.position.set(p.x(), p.y(), p.z());
    threeObject.quaternion.set(q.x(), q.y(), q.z(), q.w());
}