如何添加标签/标签以显示在多个对象的顶部,以便在用户单击对象时标签始终面向相机?
[英] 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?
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.
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.
<!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";
}
}