本质上,我想说的是我想创建一个标签或标签,它出现在对象的顶部/表面,这样当用户单击对象时,即使对象旋转,标签也始终面向相机.
我该怎么做?
我被告知要使用正交相机(但我不确定如何使用?)和标签的 CSS(参见上一篇文章:How can I make my text labels face the camera at all times? Perhaps using sprites?)
CSS 和 html 中的标签如下。但是,我也想对多个对象执行此操作,所以我想我可以在这种情况下为每个立方体列出我想要的所有标签。
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>
上一个代码:
<!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>
如果我不清楚,请告诉我。
最佳答案
我不是特别熟悉 Three.js,但这里是通常的步骤:
- 在要将标签对齐的对象表面上选择一个点。
- 使用
projector.projectVector
从世界中的点获取屏幕上的点。 - (您可能需要在此处将结果从 NDC(-1 到 1)缩放到 Canvas (0 到 canvas.width)坐标;我不确定。)
- 使用 X 和 Y 为您的标签设置 CSS 绝对定位。
这是我在 Cubes 中编写的用于执行相同操作的代码项目(不使用Three.js,但原理是一样的)。它稍微复杂一些,因为它所做的是定位元素,使其紧邻由一组点表示的对象(提供给传递给 pointGenerator
的回调) >).当物体不在相机的视野范围内时,它也会尝试做一些明智的事情。
请随意重用此代码并根据您的喜好对其进行调整。
// 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";
}
}
关于javascript - 如何添加标签/标签以显示在多个对象的顶部,以便在用户单击对象时标签始终面向相机?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16151255/