思路:
1.在页面中创建一个div标签;
2.确定标签在三维场景中的三维位置;
3.计算三维位置的屏幕坐标;
4.在每一帧的渲染中都计算一下这个三维坐标的屏幕位置,并把屏幕位置赋给标签。
具体实现:
1.创建div标签
标签样式:
.tap{
position: absolute;
background-color: MidnightBlue;
background-color:rgba(0,10,40);
border-top-left-radius: 10px;
border-bottom-right-radius:10px;
opacity: 0.5;
font-size: 4px;
color: aqua;
width: 36px;
height: 44px;
padding: 1px 1px 1px;
}
div:
<div class="tag" id="tag">
<span style="color:white;font-size: 10px;padding: 5px">楼宇名称:</span>
<span style="font-size: 11px;font-weight: bold">XXX大厦</span>
<p style="padding: 5px;margin-top: -3px;">占地面积:25541平方米</p>;
</div>
效果:
初始时我们可以将div的display设置为none,当确定它的位置后再设置为显示。
2.我们给标签指定一个场景中的位置,比如x y z为(20,30,50),要把三维坐标转换为屏幕坐标:
三维坐标转屏幕坐标的方法:
function transPosition(position){
let world_vector = new THREE.Vector3(position.x,position.y,position.z);
let vector =world_vector.project(camera);
let halfWidth = window.innerWidth / 2,
halfHeight = window.innerHeight / 2;
return {
x: Math.round(vector.x * halfWidth + halfWidth),
y: Math.round(-vector.y * halfHeight + halfHeight)
};
}
将指定的三维坐标转为屏幕坐标:
//计算三维坐标对应的屏幕坐标
var position=new THREE.Vector3(20,30,50);
var windowPosition=transPosition(position);
var left=windowPosition.x;
var top=windowPosition.y;
3.将屏幕坐标的位置赋给div,并将div的显示出来:
//设置div屏幕位置
let div = document.getElementById('tag');
div.style.display = "";
div.style.left = left + 'px';
div.style.top = top + 'px';
4.将div位置变换和赋值放入场景渲染频率中:
function render() {
divRender();
renderer.render(scene, camera);
}
function divRender() {
//计算三维坐标对应的屏幕坐标
var position=new THREE.Vector3(20,30,50);
var windowPosition=transPosition(position);
var left=windowPosition.x;
var top=windowPosition.y;
//设置div屏幕位置
let div = document.getElementById('tag');
div.style.display = "";
div.style.left = left + 'px';
div.style.top = top + 'px';
}
效果:
完整代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<link rel="stylesheet" href="css/style.css">
<style>
.tag {
position: absolute;
background-color: MidnightBlue;
background-color: rgba(0, 10, 40);
border-top-left-radius: 10px;
border-bottom-right-radius: 10px;
opacity: 0.5;
font-size: 4px;
color: aqua;
padding: 1px 1px 1px;
}
</style>
</head>
<body onload="draw();">
<div id="WebGL-output">
<div class="tag" id="tag">
<span style="color:white;font-size: 10px;padding: 5px">楼宇名称:</span>
<span style="font-size: 11px;font-weight: bold">XXX大厦</span>
<p style="padding: 5px;margin-top: -3px;">占地面积:25541平方米</p>
</div>
</div>
</body>
<script src="js/threer94.js"></script>
<script src="js/jquery.js "></script>
<script src="js/OrbitControls.js "></script>
<script src="js/stats.min.js "></script>
<script src="js/dat.gui.min.js "></script>
<script>
var scene;
var camera;
var renderer;
function init() {
scene = new THREE.Scene();
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 100000);
renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; //打开阴影
renderer.setClearAlpha(0.2);
var axis=new THREE.AxesHelper(200);;
scene.add(axis)
//添加环境光
var hemiLight = new THREE.HemisphereLight(0xffffff, 0xffffff, 1);
hemiLight.position.set(0, 500, 0);
scene.add(hemiLight);
//设置相机位置
camera.position.x = -300;
camera.position.y = 400;
camera.position.z = 300;
camera.lookAt(scene.position);
document.getElementById("WebGL-output").appendChild(renderer.domElement);
//鼠标控制器
initControls();
animate();
window.addEventListener('resize', onResize, false); //添加窗口大小监听事件
};
function initControls() {
controls2 = new THREE.OrbitControls(camera, renderer.domElement);
controls2.enableDamping = true;
controls2.dampingFactor = 1;
controls2.enableZoom = true;
controls2.zoomSpeed = 2;
controls2.autoRotate = false;
controls2.minDistance = 0;
controls2.maxDistance = 600;
controls2.enablePan = true;
controls2.keyPanSpeed = 15;
}
function animate() {
//更新控制器
controls2.update();
divRender();
renderer.render(scene, camera);
requestAnimationFrame(animate);
}
function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
function divRender() {
//计算三维坐标对应的屏幕坐标
var position = new THREE.Vector3(20, 30, 50);
var windowPosition = transPosition(position);
var left = windowPosition.x;
var top = windowPosition.y;
//设置div屏幕位置
let div = document.getElementById('tag');
div.style.display = "";
div.style.left = left + 'px';
div.style.top = top + 'px';
}
function transPosition(position) {
let world_vector = new THREE.Vector3(position.x, position.y, position.z);
let vector = world_vector.project(camera);
let halfWidth = window.innerWidth / 2,
halfHeight = window.innerHeight / 2;
return {
x: Math.round(vector.x * halfWidth + halfWidth),
y: Math.round(-vector.y * halfHeight + halfHeight)
};
}
window.onload = init;
</script>
</html>
项目中的应用:
html中标签样式:
.tap{
position: absolute;
background-color: MidnightBlue;
background-color:rgba(0,10,40);
border-top-left-radius: 10px;
border-bottom-right-radius:10px;
opacity: 0.5;
font-size: 4px;
color: aqua;
width: 36px;
height: 44px;
padding: 1px 1px 1px;
}
js中动态添加
//txt1 txt2 txt3 为div中要显示的文本,具体样式可修改 innerHTML那一行
function addDom(txt1,txt2,txt3) {
let addDivDom = document.createElement('div');
let bodyDom = document.body;
bodyDom.insertBefore(addDivDom, bodyDom.lastChild);
addDivDom.classList = 'tap';
addDivDom.innerHTML = '<span style="color:white;font-size: 10px;padding: 5px">' + txt1 + '</span>'+'<span style="font-size: 11px;font-weight: bold">'+txt2+'</span>'+'<p style="padding: 5px;margin-top: -3px;">'+txt3+'</p>';
}
render中要实时渲染修改标签的二维坐标,代码中的position为三维坐标,即标签插入时的三维坐标
function renderLabel(){
div.style.left=transPosition(position).x + 'px';
div.style.top=transPosition(position).y + 'px';
}
//三维坐标转屏幕坐标
function transPosition (position) {
let world_vector = new THREE.Vector3(position.x, position.y, position.z);
let vector = world_vector.project(camera);
let halfWidth = window.innerWidth / 2,
halfHeight = window.innerHeight / 2;
return {
x: Math.round(vector.x * halfWidth + halfWidth),
y: Math.round(-vector.y * halfHeight + halfHeight)
};
}
具体实例1:给建筑物添加标签
效果图:
(图一标签为颜色最暗的建筑的标签,图二为最高的建筑的标签)
let build=new THREE.Group();
var loader = new THREE.VRMLLoader();
//加载一个wrl格式的模型,其他格式模型同样,自建cube等不需要自建box
loader.load( 'model/h.wrl', function ( object ) {
object.castShadow = true;
build= object.children[0].children[0];
build.castShadow = true;
object.position.x=-20;
object.position.y=0;
object.position.z=-30;
scene.add(object);
let box= creatBoundingBox(object);
build.box=box;
addDom("楼宇名称 ","h 可点击","占地面积:25541平方米",box);
CubeArray.push(box);
})
function addDom(txt1,txt2,txt3,cube) {
let addDivDom = document.createElement('div');
let bodyDom = document.body;
bodyDom.insertBefore(addDivDom, bodyDom.lastChild);
addDivDom.id=cube.uuid+"_text";
addDivDom.classList = 'tap';
addDivDom.innerHTML = '<span style="color:white;font-size: 10px;padding: 5px">' + txt1 + '</span>'+'<span style="font-size: 11px;font-weight: bold">'+txt2+'</span>'+'<p style="padding: 5px;margin-top: -3px;">'+txt3+'</p>';
divIdArray.push(addDivDom.id);
}
//渲染实时更新标签位置
function render() {
cubeLabe();
renderer.render(scene, camera);
}
//只显示最近点击的楼的标签 其他标签隐藏不显示
function cubeLabe() {
if(IsSelectedMesh==true)
{
let cube=clickmesh;
let divid=cube.uuid+"_text";
for(let i=0;i<divIdArray.length;i++) {
let id = divIdArray[i];
let div = document.getElementById(id);
if(id==divid)
{
if(div!=null&&div!=undefined){
div.style.display="";
div.style.left=interface.transPosition(cube.position).x + 'px';
let posi=new THREE.Vector3(cube.position.x,(cube.position.y)*2,cube.position.z);
div.style.top=interface.transPosition(posi).y-100 + 'px';
// let number=cube.uuid;
// div.style.zIndex=number;
}
}else {
if(div!=null&&div!=undefined){
div.style.display = "none";
}
}
}
}else {
for(let i=0;i<divIdArray.length;i++) {
let id = divIdArray[i];
let div = document.getElementById(id);
if(div!=null||div!=undefined){
div.style.display = "none";
}
}
}
}
//生成模型包围盒实体,方便标签判断位置
function creatBoundingBox(object) {
//计算包围盒长宽高
let Box=new THREE.Box3();
Box.setFromObject(object);
if ( Box.isEmpty() ) return;
let min = Box.min;
let max = Box.max;
let width=max.x-min.x;
let height=max.y-min.y;
let deepth=max.z-min.z;
// console.log(width+";"+height+";"+deepth);
//计算包围盒中心点
let centerX=(max.x+min.x)/2;
let centerY=(max.y+min.y)/2;
let centerZ=(max.z+min.z)/2;
//画一个boundingbox的cube实体
let boxGeometry=new THREE.BoxGeometry(width,height,deepth);
let boxMaterial=new THREE.MeshLambertMaterial({});
let box=new THREE.Mesh(boxGeometry,boxMaterial);
box.position.set(centerX,centerY,centerZ);
return box;
}
具体实例2 点击模型表面添加标签,标签为图片
效果图:
var fixedBoard = function () {
position:null;
div:null;
image:null
}
var fixedBoardlist=[];
//添加鼠标事件 移除鼠标事件
function ImageTag() {
addEventListener('click',addFixedBoard);// 监听窗口鼠标单击事件
$(document).keydown(function (event) {
if (event.keyCode == 27) {
removeAddBoard();
}
});
}
//固定位置的标签
function addFixedBoard() {
var windowX = event.clientX;//鼠标单击位置横坐标
var windowY = event.clientY;//鼠标单击位置纵坐标
var addDivDom = document.createElement('div');
var bodyDom = document.body;
bodyDom.insertBefore(addDivDom, bodyDom.lastChild);
// addDivDom.id=cube.uuid+"_text";
addDivDom.classList = 'tap';
var img=new Image();
// var image=document.createElement("img");
var image_src="./img/002.png";
img.src=image_src;
addDivDom.appendChild(img);
var x = (windowX / window.innerWidth) * 2 - 1;//标准设备横坐标
var y = -(windowY / window.innerHeight) * 2 + 1;//标准设备纵坐标
var standardVector = new THREE.Vector3(x, y, 0.5);//标准设备坐标
//标准设备坐标转世界坐标
var worldVector = standardVector.unproject(camera);
var ray = worldVector.sub(camera.position).normalize();
//创建射线投射器对象
var raycaster = new THREE.Raycaster(camera.position, ray);
//返回射线选中的对象
var intersects = raycaster.intersectObjects(floorChildrenGroup);
console.log(intersects);
if (intersects.length > 0) {
var point=intersects[0].point;
var board=new fixedBoard();
board.position=point;
board.div=addDivDom;
board.image=img;
fixedBoardlist.push(board);
}
}
//移除鼠标单击事件绑定
function removeAddBoard() {
removeEventListener('click',addFixedBoard);
}
//实时修改渲染时的图片位置
function imagePosition() {
for(var i=0;i<fixedBoardlist.length;i++){
var position=fixedBoardlist[i].position;
var div=fixedBoardlist[i].div;
var x=interface.transPosition(position).x;
var y=interface.transPosition(position).y;
var image=fixedBoardlist[i].image;
var width=image.width;
var height=image.height;
div.style.top=y-height+ 'px';
div.style.left=x-width/2+ 'px';
}
}
//报警标签
function Warning(Istrue) {
if(Istrue){
for(var i=0;i<fixedBoardlist.length;i++){
fixedBoardlist[i].image.src='./img/003.png';
}
var button0=document.getElementById('warn');
button0.style.display="none";
var button1=document.getElementById('cancelwarn');
button1.style.display="";
}else {
for(var i=0;i<fixedBoardlist.length;i++){
fixedBoardlist[i].image.src='./img/002.png';
}
var button0=document.getElementById('warn');
button0.style.display="";
var button1=document.getElementById('cancelwarn');
button1.style.display="none";
}
}
function render(){
renderer.render(scene, camera);
imagePosition();
}