入力されたモデルのサーフェス上に点があるか判定する

与えられた点がモデルの平面上にあるかどうかを判定するコードをthreejsで書いたのでメモしておきます。 三角形ポリゴンを前提としています。

geomにはTHREE.Geometry、ptにはTHREE.Vector3が渡されるとします。 (THREE.Geometryは公式ではもう廃止されてた気もする。。)

function is_on_surface(geom, pt) {
    // Geometryを構成するする三角形面のうち、条件を満たすものがあるかを判定
    return geom.faces.some(function(f) {
        // 三角形の各頂点を取得する
        const v1 = geom.vertices[f.a];
        const v2 = geom.vertices[f.b];
        const v3 = geom.vertices[f.c];
        
        const v12 = new THREE.Vector3().subVectors(v2, v1);
        const v23 = new THREE.Vector3().subVectors(v3, v2);

        // 三角形の法線ベクトルを算出
        const n = new THREE.Vector3().crossVectors(v12, v23).normalize();

        // 三角形上の頂点から入力点までのベクトル
        const v1p = new THREE.Vector3().subVectors(pt, v1);

        // 三角形の平面と入力点までの距離を算出
        const dist = Math.abs(v1p.dot(n));

        // 平面との距離が0.0001以上なら同一平面にない点と判定
        if (dist >= 0.0001) return false;

        const v31 = new THREE.Vector3().subVectors(v1, v3);
        const v2p = new THREE.Vector3().subVectors(pt, v2);
        const v3p = new THREE.Vector3().subVectors(pt, v3);

        const n1 = new THREE.Vector3().crossVectors(v12, v2p).normalize();
        const n2 = new THREE.Vector3().crossVectors(v23, v3p).normalize();
        const n3 = new THREE.Vector3().crossVectors(v31, v1p).normalize();
        return n1.dot(n2) > 0 && n1.dot(n3) > 0;
    });
}

簡単に解説しておくと2ステップで判定します。

まず最初に、入力された点ptが平面上にあるかどうかを判定します。 平面上にあるというのは平面と点の距離が十分小さいことで判定できますし、 平面と点の距離は、面の法線ベクトルと、面上の任意の点からのベクトルの内積を求めれば、求まります。

今回は誤差もでるので以下のように0.0001以下なら平面上とみなす、としました。 値を見た感じ、もっと厳しく判定しても問題なさそうでしたが、条件によっては調整が必要かもです。

if (dist >= 0.0001) return false;

次に、点が三角形の内部にあるか外部にあるかを判定します。これも外積を使えば簡単に判定できます。 この辺の解説はあちこちにあるので省略します。 www.sousakuba.com

上記の両方を満たせば、三角形ポリゴン内部に点があるといえます。 これをGeometryのポリゴンすべてで実行すれば、モデル表面に点があるかどうかを判定できます。

あと一言添えるとすると、threejsのVector3はmutableなので、crossとか使うと外積元の変数が変わってしまうところに気を付ける必要はあります。