THREE.JS : 带有光线投射器和透视相机的点击事件

Posted

技术标签:

【中文标题】THREE.JS : 带有光线投射器和透视相机的点击事件【英文标题】:THREE.JS : click event with raycaster and perspective camera 【发布时间】:2022-01-24 01:45:12 【问题描述】:

我想在three.js 中为网页创建一个菜单,其中每个选项卡都由一个对象(2D 矩形)具体化,我们通过单击右侧矩形访问网站页面。为此,我读到我必须使用当我右键单击鼠标时触发的光线投射器,然后使用相交对象的数组来查看我单击了哪些对象。

我在互联网上尝试了很多教程,但从未成功获得用于右键单击的功能性 raycaster 和 eventListener。 raycast 函数只是从 three.js 文档中复制/粘贴。

这是我的代码(称为 script.js),您知道我做错了什么吗?或者您有什么建议可以改进吗?提前感谢您的帮助!

var camera, scene, rendu;
var geometrie, materiau, mesh;
var controle;
var couleur, plan, onglet1, onglet2, onglet3, onglet4, onglet5, onglet6;
var r, t;
var rayon, souris;

init();

function init() 
    r = 3;
    t = 1.1;
    // ---------- scene et camera --------- //
    camera = new THREE.PerspectiveCamera( 70 , window.innerWidth / window.innerHeight , 0.01 , 10 );
    camera.position.set( 0 , 0 , 4 );
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000);

    // --------- cube --------- //
    //materiau = new THREE.MeshBasicMaterial(  color: 0xffffff);
    //geometrie = new THREE.BoxGeometry( t , 3*t , t );
    //mesh = new THREE.Mesh( geometrie, materiau );
    //scene.add( mesh );

    // --------- onglets ---------- //
    couleur = new THREE.MeshBasicMaterial( color: 0x031f3c , side: THREE.DoubleSide  );
    plan = new THREE.PlaneGeometry( 0.75 , 0.4 );

    onglet1 = new THREE.Mesh( plan , couleur );
    onglet1.position.set( 0, 0, r );
    scene.add( onglet1 );

    onglet2 = new THREE.Mesh( plan , couleur );
    onglet2.position.set( r*Math.sqrt(3/4), 0, 0.5*r );
    onglet2.rotation.y = Math.PI / 3;
    scene.add( onglet2 );

    onglet3 = new THREE.Mesh( plan , couleur );
    onglet3.position.set( r*Math.sqrt(3/4), 0, -0.5*r );
    onglet3.rotation.y = 2 *Math.PI / 3;
    scene.add( onglet3 );

    onglet4 = new THREE.Mesh( plan , couleur );
    onglet4.position.set( 0, 0, -r );
    scene.add( onglet4 );

    onglet5 = new THREE.Mesh( plan , couleur );
    onglet5.position.set( -r*Math.sqrt(3/4), 0, -0.5*r );
    onglet5.rotation.y = -2*Math.PI / 3;
    scene.add( onglet5 );

    onglet6 = new THREE.Mesh( plan , couleur );
    onglet6.position.set( -r*Math.sqrt(3/4), 0, 0.5*r );
    onglet6.rotation.y = -Math.PI / 3;
    scene.add( onglet6 );

    // ---------- rendu ------------- //
    rendu = new THREE.WebGLRenderer(  antialias: true );
    rendu.setSize( window.innerWidth, window.innerHeight );
    rendu.setPixelRatio(window.devicePixelRatio);
    rendu.setAnimationLoop( animation );
    document.body.appendChild( rendu.domElement );

    // ------------- liens sur onglets ------------- //
    //window.addEventListener('click', onclick, false);
    // ecouter l'event de clic sur la souris
    // on ajoute un capteur de rayon qui regarde si on croise un objet de la scene
    // si c'est le cas on fait ce qu'on veut en fonction de l'objet

    rayon = new THREE.Raycaster();
    rendu.domElement.addEventListener( 'click', raycast, false );



function animation() 
    rendu.render( scene, camera );
    controle.update();


function raycast ( e ) 
    //1. sets the mouse position with a coordinate system where the center
    // of the screen is the origin
    souris.x = ( e.clientX / window.innerWidth ) * 2 - 1;
    souris.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
    //2. set the picking ray from the camera position and mouse coordinates
    rayon.setFromCamera( souris, camera );
    //3. compute intersections
    var intersections = rayon.intersectObjects( scene.children );
    intersections[0]


// ---------- orbitControls --------- //
controle = new THREE.OrbitControls(camera, rendu.domElement);
controle.minPolarAngle = Math.PI / 2;
controle.maxPolarAngle = Math.PI / 2;
controle.autoRotate = true;
controle.autoRotateSpeed = -0.1;
controle.enableZoom = false;
controle.mouseButtons = 
    LEFT: THREE.MOUSE.ROTATE,
    MIDDLE: THREE.MOUSE.DOLLY,
    RIGHT: THREE.MOUSE.ROTATE


// ----------- redimensionnement fenetre ------------ //
window.addEventListener('resize', function() 
    camera.aspect = window.innerWidth / window.innerHeight ;
    camera.updateProjectionMatrix();
    rendu.setSize(window.innerWidth, window.innerHeight);
, false);

let loader = new THREE.GLTFLoader();
loader.load('voiture/scene.gltf', function(gltf) 
    voiture = gltf.scene.children[0];
    //voiture.scale.set(0.5, 0.5, 0.5);
    scene.add(gltf.scene);
    rendu.render(scene, camera);
);

这是我的 html 代码(称为 index.html)

<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>test</title>

    <style>
        bodymargin: 0px;
        canvaswidth: 100%; height: 100%;
    </style>
</head>


<body>
    <script src="js/build/three.js"></script>
    <script src="js/build/three.min.js"></script>
    <script src="js/OrbitControls.js"></script>
    <script src="js/GLTFLoader.js"></script>
    <script src="js/script.js"></script>
</body>
</html>

【问题讨论】:

压缩并附加所有依赖项:three.js、three.min.js、OrbitControls.js、GLTFLoader.js、script.js 感谢维克托!但它有什么用呢?我不明白 用于测试和调试因为我不知道你使用的是三个js的哪个版本... 如何将它们压缩到我的代码中?之后我的 html 文件是否能够解压缩以读取它们? 前两天我从github下载了三个js文件,估计是最新版本。此外,我认为文件 three.min.js 对任何目的都没有用,因为无论我添加与否,它都不会改变任何东西 【参考方案1】:

souris 初始化为Vector2 后,您将投射光线,然后在尝试更改任何内容之前检查它是否真的与对象相交。

在第一个相交的对象上设置新材质,以避免更改场景中所有对象的颜色,因为它们共享相同的材质。

function raycast ( e ) 
    //1. sets the mouse position with a coordinate system where the center
    // of the screen is the origin
    souris = new THREE.Vector2();
    souris.x = ( e.clientX / window.innerWidth ) * 2 - 1;
    souris.y = - ( e.clientY / window.innerHeight ) * 2 + 1;
    //2. set the picking ray from the camera position and mouse coordinates
    rayon.setFromCamera( souris, camera );
    //3. compute intersections
    var intersections = rayon.intersectObjects( scene.children );

    if (intersections.length) 
        intersections[0].object.material = 
            new THREE.MeshBasicMaterial( 
                color: 0xff0000, 
                side: THREE.DoubleSide 
            );

        if (intersections[0].object.url)  window.open(intersections[0].object.url, '_blank'); 
    

另外,不需要同时导入three.jsthree.min.js,删除其中一个。

希望这能解决您的问题...

【讨论】:

【参考方案2】:

我相信您的问题源于未将您的 souris 变量初始化为 THREE.Vector2()。您的代码归结为:

var souris;
souris.x = xxx;
souris.y = yyy;

var rayon = new THREE.Raycaster();
rayon.setFromCamera( souris, camera );

Raycaster.setFromCamera requires you use a Vector2 作为它的第一个参数,但您从未创建过Vector2

要解决此问题,只需确保在分配其值之前声明 souris = THREE.Vector2()

var souris = THREE.Vector2();
souris.x = xxx;
souris.y = yyy;

var rayon = new THREE.Raycaster();
rayon.setFromCamera( souris, camera );

【讨论】:

谢谢!我在 raycast 函数中初始化了 souris,并为我的对象添加了颜色变化,以查看我是否做得好,但它从未改变颜色...这是我现在的函数 raycast:``` function raycast (e) var souris = new三.Vector2(); souris.x = ( e.clientX / window.innerWidth ) * 2 - 1; souris.y = - ( e.clientY / window.innerHeight ) * 2 + 1; rayon.setFromCamera(souris, 相机); var 交叉点 = rayon.intersectObjects(scene.children);交叉点[0].material.color.setHex(0xff0000); ``` @vektor 没必要攻击我。向 OP 索要所有依赖项的压缩文件会让他白白浪费时间。对不起,如果我觉得我很粗鲁。无论如何,我赞成您随后的回答,因为它是解决问题的好答案。

以上是关于THREE.JS : 带有光线投射器和透视相机的点击事件的主要内容,如果未能解决你的问题,请参考以下文章

如何在我的光线投射器中修复扭曲的墙壁?

three.js使用gpu选取物体并计算交点位置

在three.js中使用正交相机进行不精确的光线投射

Three.js - 将光线从物体发送到相机

如何在 Three.js 中获取透视相机的角度值?

Three.js raycaster 可以与组相交吗?