activePoint变量为threejs世界坐标,获取到可以传给后端做永久保存。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
body {
margin: 0;
}
</style>
</head>
<body>
<div id="WebGl-output"></div>
<span id="tip" style="position: absolute;color:red;top:0;left:0;">点击右键添加标注</span>
<script src="three.js"></script>
<script src="OrbitControls.js"></script>
<script>
let doc = document
let tags = []
let tagObject = new THREE.Object3D()
let screenVector = new THREE.Vector3()
let raycaster = new THREE.Raycaster()
let vector = new THREE.Vector3();
let activePoint = null
let data = [
{
"title": "test1",
"point": {
"x": -49.12874919105969,
"y": -7.4075448683018,
"z": -33.345400819742046
}
},
{
"title": "test2",
"point": {
"x": -29.14179077971636,
"y": -11.729570788835838,
"z": -50.88589614945478
}
},
{
"title": "test3",
"point": {
"x": -34.97837170750319,
"y": -0.4862641623377102,
"z": 48.69562416852437
}
},
{
"title": "test4",
"point": {
"x": -57.499764366164065,
"y": 16.241197387208377,
"z": -2.6968122977083366
}
}
]
/**
* 禁用右键
**/
window.oncontextmenu = function (e) {
e.preventDefault();
}
let scene = new THREE.Scene()
let camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000)
camera.position.set(0, 0, 0.1);
let renderer = new THREE.WebGLRenderer()
renderer.setPixelRatio(window.devicePixelRatio)
renderer.setSize(window.innerWidth, window.innerHeight)
doc.getElementById('WebGl-output').appendChild(renderer.domElement)
let geometry = new THREE.SphereGeometry(60, 40, 40)
geometry.scale(1, 1, -1)
let texture = new THREE.TextureLoader().load('snow.jpg')
let material = new THREE.MeshBasicMaterial({
map: texture,
color: '#ffffff',
// side: 2
})
let mesh = new THREE.Mesh(geometry, material)
scene.add(mesh)
let controls = new THREE.OrbitControls(camera, renderer.domElement)
controls.rotateSpeed = -0.25;
scene.add(tagObject);
let div = doc.getElementById('tip')
/**
* 创建json数据
**/
data.map(item => {
sign(item.point, item.title)
})
/**
* 鼠标移动触发
**/
function onMouseMove(event) {
// 屏幕坐标转标准设备坐标
vector.set((event.clientX / window.innerWidth) * 2 - 1, -(event.clientY / window.innerHeight) * 2 + 1, 0);
// 将标准设备坐标转为世界坐标
vector.unproject(camera);
raycaster = new THREE.Raycaster(camera.position, vector.sub(camera.position).normalize());
let intersects = raycaster.intersectObjects([mesh]);
if (intersects.length > 0) {
activePoint = intersects[0].point;
let point = toScreenPosition({point: activePoint})
div.style.left = point.x + 'px';
div.style.top = point.y + 20 + 'px';
}
}
/**
* 鼠标点击触发
**/
function onMouseDown(event) {
if (event.buttons === 2) {
console.log(JSON.stringify(activePoint)) // 传给后台的参数
sign(activePoint)
}
}
/**
* 标记
**/
function sign(position, title) {
let tagMesh = new THREE.Mesh(new THREE.SphereGeometry(1), new THREE.MeshBasicMaterial({color: '#ffff00'}));
tagMesh.position.copy(position);
tagObject.add(tagMesh);
let tagElement = doc.createElement("div");
tagElement.innerHTML = `<span>${!title ? '标记' + (tags.length + 1) : title}</span>`;
tagElement.style.background = "red";
tagElement.style.position = "absolute";
tagMesh.updateTag = function () {
// 超出相机视野则隐藏
if (isOffScreen(tagMesh, camera)) {
tagElement.style.display = "none";
} else {
let position = toScreenPosition({obj: tagMesh});
tagElement.style.display = "block";
tagElement.style.left = `${position.x - tagElement.offsetWidth / 2}px`;
tagElement.style.top = `${position.y + 12}px`;
}
}
tagMesh.updateTag();
doc.getElementById("WebGl-output").appendChild(tagElement);
tags.push(tagMesh);
}
/**
* 世界坐标转屏幕坐标(3D -> 2D)
**/
function toScreenPosition({obj = null, point = null}) {
point ? screenVector.set(...point) : screenVector.set();
// 屏幕坐标系中心
let widthHalf = renderer.getContext().canvas.width / 2;
let heightHalf = renderer.getContext().canvas.height / 2;
if (obj) {
// 更新物体及其后代的全局变换
obj.updateMatrixWorld();
// 提取位置相关的分量
screenVector.setFromMatrixPosition(obj.matrixWorld);
}
// 世界坐标转标准设备坐标。范围[-1,1]
screenVector.project(camera)
//标准设备坐标转屏幕坐标(2D)
screenVector.x = screenVector.x * widthHalf + widthHalf;
screenVector.y = -screenVector.y * heightHalf + heightHalf;
return {
x: screenVector.x,
y: screenVector.y
};
}
/**
* 判断是否超出相机视野
**/
function isOffScreen(obj) {
let frustum = new THREE.Frustum(); //Frustum用来确定相机的可视区域
let cameraViewProjectionMatrix = new THREE.Matrix4();
cameraViewProjectionMatrix.multiplyMatrices(camera.projectionMatrix, camera.matrixWorldInverse); //获取相机的法线
frustum.setFromProjectionMatrix(cameraViewProjectionMatrix); //设置frustum沿着相机法线方向
return !frustum.intersectsObject(obj);
}
doc.addEventListener("pointerdown", onMouseDown, false);
doc.addEventListener("pointermove", onMouseMove, false);
render()
function render() {
requestAnimationFrame(render)
controls.update();
tags.forEach(function (tagMesh) {
tagMesh.updateTag();
});
renderer.render(scene, camera)
}
</script>
</body>
</html>
代码修改自 github.com/heavis/thre…