如何添加标签/标签以显示在多个对象的顶部,以便在用户单击对象时标签始终面向相机? [英] How do I add a tag/label to appear on top of several objects so that the tag always faces the camera when the user clicks the object?

查看:21
本文介绍了如何添加标签/标签以显示在多个对象的顶部,以便在用户单击对象时标签始终面向相机?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

本质上,我想说的是我想创建一个出现在对象顶部/表面的标签或标签,以便在用户单击对象时标签始终面向相机,即使对象旋转.

我该怎么做?

有人告诉我使用正交相机(但我不确定如何使用?)和 CSS 标签(请参阅上一篇文章:如何让我的文本标签始终面向相机?也许使用精灵?)

CSS 和 html 中的标签如下.但是,我也想为多个对象执行此操作,因此在这种情况下,我想我可以为每个多维数据集列出我想要的所有标签.

CSS:

label {垂直对齐:中间;显示:表格单元格;背景色:#99FFCC;边框:1px 实心 #008000;宽度:150px;}

HTML:

<label>立方体1</label>

以前的代码:

<html lang="zh-cn"><头><title>three.js 画布 - 交互式 - 立方体</title><meta charset="utf-8"><meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0"><风格>身体 {字体系列:等宽;背景色:#f0f0f0;边距:0px;溢出:隐藏;}</风格><身体><script src="js/three.min.js"></script><script src="js/stats.min.js"></script><脚本>var 容器,统计数据;var 相机、场景、投影仪、渲染器;var 投影仪,鼠标 = { x: 0, y: 0 }, INTERSECTED;var 粒子材料;var currentLabel = null;var 对象 = [];在里面();动画();函数初始化(){container = document.createElement('div');document.body.appendChild(容器);var info = document.createElement('div');info.style.position = '绝对';info.style.top = '10px';info.style.width = '100%';info.style.textAlign = 'center';info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a>- 可点击的对象;container.appendChild( info );camera = new THREE.PerspectiveCamera( 70, window.innerWidth/window.innerHeight, 1, 10000 );camera.position.set( 0, 300, 500 );场景 = 新的 THREE.Scene();var geometry = new THREE.CubeGeometry(100, 100, 100);for ( var i = 0; i <10; i ++ ) {var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );object.position.x = Math.random() * 800 - 400;object.position.y = Math.random() * 800 - 400;object.position.z = Math.random() * 800 - 400;object.scale.x = Math.random() * 2 + 1;object.scale.y = Math.random() * 2 + 1;object.scale.z = Math.random() * 2 + 1;object.rotation.x = Math.random() * 2 * Math.PI;object.rotation.y = Math.random() * 2 * Math.PI;object.rotation.z = Math.random() * 2 * Math.PI;object.label = "对象" + i;场景添加(对象);对象.推(对象);}var PI2 = Math.PI * 2;粒子材料 = 新的 THREE.ParticleCanvasMaterial( {颜色:0x000000,程序:函数(上下文){context.beginPath();context.arc( 0, 0, 1, 0, PI2, true );context.closePath();上下文填充();}});投影仪 = 新的 THREE.Projector();renderer = new THREE.CanvasRenderer();renderer.setSize( window.innerWidth, window.innerHeight );container.appendChild(renderer.domElement);统计=新统计();stats.domElement.style.position = '绝对';stats.domElement.style.top = '0px';container.appendChild(stats.domElement);document.addEventListener('mousedown', onDocumentMouseDown, false );//window.addEventListener('resize', onWindowResize, false );}函数 onWindowResize() {camera.aspect = window.innerWidth/window.innerHeight;相机.updateProjectionMatrix();renderer.setSize( window.innerWidth, window.innerHeight );}函数 onDocumentMouseDown( 事件 ) {event.preventDefault();var vector = new THREE.Vector3( ( event.clientX/window.innerWidth ) * 2 - 1, - ( event.clientY/window.innerHeight ) * 2 + 1, 0.5 );投影仪.unprojectVector(矢量,相机);var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());var intersects = raycaster.intersectObjects(objects);if ( intersects.length > 0 ) {如果(相交[0].对象!=相交){//将先前的交集对象(如果存在)恢复为其原始颜色如果(相交){INTERSECTED.material.color.setHex(INTERSECTED.currentHex);}//将最近对象的引用存储为当前交叉对象相交 = 相交 [0].object;//存储最近对象的颜色(用于稍后恢复)INTERSECTED.currentHex = INTERSECTED.material.color.getHex();//为最近的对象设置一个新颜色INTERSECTED.material.color.setHex(0xffff00);var canvas1 = document.createElement('canvas');var context1 = canvas1.getContext('2d');context1.font = "粗体 40px Arial";context1.fillStyle = "rgba(255,0,0,0.95)";context1.fillText(INTERSECTED.label, 0, 50);//画布内容将用于纹理var texture1 = new THREE.Texture(canvas1)纹理1.needsUpdate = true;var material1 = new THREE.MeshBasicMaterial( {map: texture1, side:THREE.DoubleSide } );material1.transparent = true;var mesh1 = new THREE.Mesh(new THREE.PlaneGeometry(canvas1.width, canvas1.height),材料1);mesh1.position = intersects[0].point;如果(当前标签)场景.删除(当前标签);场景.添加(mesh1);currentLabel = 网格 1;}else//没有交叉点{//将先前的交集对象(如果存在)恢复为其原始颜色如果(相交){console.log("你好");INTERSECTED.material.color.setHex(INTERSECTED.currentHex);}//删除之前的交集对象引用//通过将当前交集对象设置为无"相交 = 空;网格 1 = 空;mesh1.position = intersects[0].point;场景.添加(mesh1);}//varparticle = new THREE.Particle(particleMaterial);//particle.position = intersects[0].point;//particle.scale.x =particle.scale.y = 8;//scene.add(粒子);}/*//解析所有人脸for ( var i in intersects ) {相交[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );}*/}//函数动画(){requestAnimationFrame( 动画 );使成为();stats.update();}无功半径 = 600;变量 theta = 0;函数渲染(){θ += 0.1;camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );相机.看(场景.位置);renderer.render(场景,相机);}

如果我不清楚,请告诉我.

我对 Three.js 不是特别熟悉,但这里是通常的步骤:

这是我在我的 Cubes 项目中编写的代码(不使用 Three.js,但原理是相同的).它稍微复杂一些,因为它所做的是定位元素,使其紧挨一个由一组点表示的对象(这些点提供给传递给 pointGenerator).当物体不在相机的视野中时,它也会尝试做一些明智的事情.

随意重用此代码并根据您的喜好进行调整.

//将叠加 HTML 元素放置在提供的一组点附近.函数 positionByWorld(元素,keepInBounds,pointGenerator){var canvasStyle = window.getComputedStyle(theCanvas,null);var canvasWidth = parseInt(canvasStyle.width, 10);var canvasHeight = parseInt(canvasStyle.height, 10);var elemStyle = window.getComputedStyle(element, null);var elemWidth = parseInt(elemStyle.width, 10);var elemHeight = parseInt(elemStyle.height, 10);var slx = 无穷大;var 狡猾 = 无穷大;var shx = -Infinity;var 害羞 = -Infinity;var toScreenPoint = vec4.create();点生成器(函数(x,y,z,w){toScreenPoint[0] = x;toScreenPoint[1] = y;toScreenPoint[2] = z;toScreenPoint[3] = w;renderer.transformPoint(toScreenPoint);toScreenPoint[0]/= toScreenPoint[3];toScreenPoint[1]/= toScreenPoint[3];toScreenPoint[2]/= toScreenPoint[3];如果 (toScreenPoint[3] > 0) {slx = Math.min(slx, toScreenPoint[0]);shx = Math.max(shx, toScreenPoint[0]);sly = Math.min(sly, toScreenPoint[1]);shy = Math.max(shy, toScreenPoint[1]);}});if (shx > -1 && shy > -1 && slx < 1 && sly < 1/* 可见 */) {//转换为屏幕slx = (slx + 1)/2 * 画布宽度;//shx = (shx + 1)/2 * canvasWidth;//sly = (sly + 1)/2 * canvasHeight;害羞 = (害羞 + 1)/2 * 画布高度;如果(保持边界){slx = Math.max(0, Math.min(canvasWidth - elemWidth, slx));shy = Math.max(0, Math.min(canvasHeight - elemHeight, shy));}element.style.left = slx + "px";element.style.bottom = 害羞 + "px";} 别的 {element.style.left = canvasWidth + "px";}}

GitHub 上的永久链接

Essentially, what I am saying is I want to create a tag or label that appears on top of/on the surface of an object so that the tag always faces the camera when the user clicks the object even when the object is rotated.

How do I go about doing so?

I was told to use Orthogonal camera (but I'm not sure how?) and CSS for the label (see previous post: How can I make my text labels face the camera at all times? Perhaps using sprites?)

The label in CSS and html is below. However, I want to do this for several objects as well, so I guess I can make a list of all the tags I want for each cube in this case.

CSS:

label {
    vertical-align: middle;
    display: table-cell;
    background-color : #99FFCC;
border: 1px solid #008000;
width: 150px;
}

HTML:

<div id="Cube1">
    <label>Cube 1</label>
</div>

Previous Code:

<!DOCTYPE html>
<html lang="en">
<head>
        <title>three.js canvas - interactive - cubes</title>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
        <style>
                body {
                        font-family: Monospace;
                        background-color: #f0f0f0;
                        margin: 0px;
                        overflow: hidden;
                }
        </style>
</head>
<body>

        <script src="js/three.min.js"></script>

        <script src="js/stats.min.js"></script>

        <script>

                var container, stats;
                var camera, scene, projector, renderer;
                var projector, mouse = { x: 0, y: 0 }, INTERSECTED;
                var particleMaterial;
                var currentLabel = null;

                var objects = [];

                init();
                animate();

                function init() {

                        container = document.createElement( 'div' );
                        document.body.appendChild( container );

                        var info = document.createElement( 'div' );
                        info.style.position = 'absolute';
                        info.style.top = '10px';
                        info.style.width = '100%';
                        info.style.textAlign = 'center';
                        info.innerHTML = '<a href="http://threejs.org" target="_blank">three.js</a> - clickable objects';
                        container.appendChild( info );

                        camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 10000 );
                        camera.position.set( 0, 300, 500 );

                        scene = new THREE.Scene();

                        var geometry = new THREE.CubeGeometry( 100, 100, 100 );

                        for ( var i = 0; i < 10; i ++ ) {

                                var object = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, opacity: 0.5 } ) );
                                object.position.x = Math.random() * 800 - 400;
                                object.position.y = Math.random() * 800 - 400;
                                object.position.z = Math.random() * 800 - 400;

                                object.scale.x = Math.random() * 2 + 1;
                                object.scale.y = Math.random() * 2 + 1;
                                object.scale.z = Math.random() * 2 + 1;

                                object.rotation.x = Math.random() * 2 * Math.PI;
                                object.rotation.y = Math.random() * 2 * Math.PI;
                                object.rotation.z = Math.random() * 2 * Math.PI;

                                object.label = "Object " + i;

                                scene.add( object );

                                objects.push( object );

                        }

                        var PI2 = Math.PI * 2;
                        particleMaterial = new THREE.ParticleCanvasMaterial( {

                                color: 0x000000,
                                program: function ( context ) {

                                        context.beginPath();
                                        context.arc( 0, 0, 1, 0, PI2, true );
                                        context.closePath();
                                        context.fill();

                                }

                        } );

                        projector = new THREE.Projector();

                        renderer = new THREE.CanvasRenderer();
                        renderer.setSize( window.innerWidth, window.innerHeight );

                        container.appendChild( renderer.domElement );

                        stats = new Stats();
                        stats.domElement.style.position = 'absolute';
                        stats.domElement.style.top = '0px';
                        container.appendChild( stats.domElement );

                        document.addEventListener( 'mousedown', onDocumentMouseDown, false );

                        //

                        window.addEventListener( 'resize', onWindowResize, false );

                }

                function onWindowResize() {

                        camera.aspect = window.innerWidth / window.innerHeight;
                        camera.updateProjectionMatrix();

                        renderer.setSize( window.innerWidth, window.innerHeight );

                }

                function onDocumentMouseDown( event ) {

                        event.preventDefault();

                        var vector = new THREE.Vector3( ( event.clientX / window.innerWidth ) * 2 - 1, - ( event.clientY / window.innerHeight ) * 2 + 1, 0.5 );
                        projector.unprojectVector( vector, camera );

                        var raycaster = new THREE.Raycaster( camera.position, vector.sub( camera.position ).normalize() );

                        var intersects = raycaster.intersectObjects( objects );

                        if ( intersects.length > 0 ) {

                        if ( intersects[ 0 ].object != INTERSECTED ) 
                                        {

                                         // restore previous intersection object (if it exists) to its original color
                                        if ( INTERSECTED ) {


                                        INTERSECTED.material.color.setHex( INTERSECTED.currentHex ); } 

                                        // store reference to closest object as current intersection object
                                        INTERSECTED = intersects[ 0 ].object;
                                        // store color of closest object (for later restoration)
                                        INTERSECTED.currentHex = INTERSECTED.material.color.getHex();
                                        // set a new color for closest object
                                        INTERSECTED.material.color.setHex( 0xffff00 );

                                        var canvas1 = document.createElement('canvas');
                                        var context1 = canvas1.getContext('2d');
                                        context1.font = "Bold 40px Arial";
                                        context1.fillStyle = "rgba(255,0,0,0.95)";
                                        context1.fillText(INTERSECTED.label, 0, 50);

                                        // canvas contents will be used for a texture
                                        var texture1 = new THREE.Texture(canvas1) 
                                        texture1.needsUpdate = true;

                                        var material1 = new THREE.MeshBasicMaterial( {map: texture1, side:THREE.DoubleSide } );
                                        material1.transparent = true;

                                        var mesh1 = new THREE.Mesh(
                                        new THREE.PlaneGeometry(canvas1.width, canvas1.height),
                                        material1



                                );
                                        mesh1.position = intersects[0].point;
                                        if (currentLabel)
                                                scene.remove(currentLabel);
                                        scene.add( mesh1 );                             
                                        currentLabel = mesh1;
                        } 

                        else // there are no intersections
                                        {
                                // restore previous intersection object (if it exists) to its original color
                                if ( INTERSECTED ) {
                                        console.log("hello");
                                        INTERSECTED.material.color.setHex( INTERSECTED.currentHex );
                                        }
                                        // remove previous intersection object reference
                                        //     by setting current intersection object to "nothing"
                                        INTERSECTED = null;
                                        mesh1 = null; 
                                        mesh1.position = intersects[0].point; 
                                        scene.add( mesh1 );

                                        }






                                //var particle = new THREE.Particle( particleMaterial );
                                //particle.position = intersects[ 0 ].point;
                                //particle.scale.x = particle.scale.y = 8;
                                //scene.add( particle );

                        }

                        /*
                        // Parse all the faces
                        for ( var i in intersects ) {

                                intersects[ i ].face.material[ 0 ].color.setHex( Math.random() * 0xffffff | 0x80000000 );

                        }
                        */


                }

                //

                function animate() {

                        requestAnimationFrame( animate );

                        render();
                        stats.update();

                }

                var radius = 600;
                var theta = 0;

                function render() {

                        theta += 0.1;

                        camera.position.x = radius * Math.sin( THREE.Math.degToRad( theta ) );
                        camera.position.y = radius * Math.sin( THREE.Math.degToRad( theta ) );
                        camera.position.z = radius * Math.cos( THREE.Math.degToRad( theta ) );
                        camera.lookAt( scene.position );

                        renderer.render( scene, camera );

                }

        </script>

</body>

Please let me know if I'm being unclear.

解决方案

I'm not especially familiar with Three.js, but here's the usual steps:

  • Choose a point on the surface of your object you want to align the label to.
  • Use projector.projectVector to get an on-screen point from the in-world point.
  • (You may need to scale the result from NDC (-1 to 1) to canvas (0 to canvas.width) coordinates here; I'm not sure.)
  • Use the X and Y to set CSS absolute positioning for your label.

Here's code I wrote to do the same thing in my Cubes project (not using Three.js, but the principles are the same). It is slightly more complex because what it does is position the element so that it is next to an object represented by a set of points (which are provided to the callback passed to pointGenerator). It also tries to do sensible things when the object is out of view of the camera.

Feel free to reuse this code and adapt it to your liking.

// Position an overlay HTML element adjacent to the provided set of points.
function positionByWorld(element, keepInBounds, pointGenerator) {
  var canvasStyle = window.getComputedStyle(theCanvas,null);
  var canvasWidth = parseInt(canvasStyle.width, 10);
  var canvasHeight = parseInt(canvasStyle.height, 10);

  var elemStyle = window.getComputedStyle(element, null);
  var elemWidth = parseInt(elemStyle.width, 10);
  var elemHeight = parseInt(elemStyle.height, 10);

  var slx = Infinity;
  var sly = Infinity;
  var shx = -Infinity;
  var shy = -Infinity;
  var toScreenPoint = vec4.create();

  pointGenerator(function (x, y, z, w) {
    toScreenPoint[0] = x;
    toScreenPoint[1] = y;
    toScreenPoint[2] = z;
    toScreenPoint[3] = w;
    renderer.transformPoint(toScreenPoint);
    toScreenPoint[0] /= toScreenPoint[3];
    toScreenPoint[1] /= toScreenPoint[3];
    toScreenPoint[2] /= toScreenPoint[3];
    if (toScreenPoint[3] > 0) {
      slx = Math.min(slx, toScreenPoint[0]);
      shx = Math.max(shx, toScreenPoint[0]);
      sly = Math.min(sly, toScreenPoint[1]);
      shy = Math.max(shy, toScreenPoint[1]);
    }
  });

  if (shx > -1 && shy > -1 && slx < 1 && sly < 1 /* visible */) {
    // convert to screen
    slx = (slx + 1) / 2 * canvasWidth;
    //shx = (shx + 1) / 2 * canvasWidth;
    //sly = (sly + 1) / 2 * canvasHeight;
    shy = (shy + 1) / 2 * canvasHeight;
    if (keepInBounds) {
      slx = Math.max(0, Math.min(canvasWidth - elemWidth, slx));
      shy = Math.max(0, Math.min(canvasHeight - elemHeight, shy));
    }
    element.style.left   = slx + "px";
    element.style.bottom = shy + "px";
  } else {
    element.style.left   = canvasWidth + "px";
  }
}

Permalink on GitHub

这篇关于如何添加标签/标签以显示在多个对象的顶部,以便在用户单击对象时标签始终面向相机?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆