需求:鼠标略过模型出现三维坐标轴,通过点击三维坐标轴,在 x、y、z 轴上移动,并且通过点中模型任意拖拽模型。
本质上,使用鼠标拖动三维模型,就是把鼠标的拖动距离,转化为三维模型的位置或角度变化量,使用three、js的拖拽控件·DragControls.js·就可以很容容易实现。
示例浏览地址:
https://ithanmang.gitee.io/threejs/home/201807/20180703/03-raycaster-dragControls.html
<!--拖拽控件-->
<script src="../../libs/examples/js/controls/DragControls.js"></script>
<!--可视化平移控件-->
<script src="../../libs/examples/js/controls/TransformControls.js"></script>
// 添加拖拽控件
function initDragControls() {
// 添加平移控件
var transformControls = new THREE.TransformControls(camera, renderer.domElement);
scene.add(transformControls);
// 过滤不是 Mesh 的物体,例如辅助网格对象
var objects = [];
for (let i = 0; i < scene.children.length; i++) {
if (scene.children[i].isMesh) {
objects.push(scene.children[i]);
}
}
// 初始化拖拽控件
var dragControls = new THREE.DragControls(objects, camera, renderer.domElement);
// 鼠标略过事件
dragControls.addEventListener('hoveron', function (event) {
// 让变换控件对象和选中的对象绑定
transformControls.attach(event.object);
});
// 开始拖拽
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
// 拖拽结束
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
});
}
注意:拖拽的时候回影响其他的事件,最好处理一下
TransformControls
方法transformControls
配合拖拽控件就可以实现可视化操作三维模型。
通过拖拽控件选中一个三维模型,然后通过拖拽控件对象的方法attach()
可以把选中的三维模型与控件transformControls
绑定, 可以把控件对象transformControls
理解为一个特殊的三维模型。
例如平移状态,控件对象TransformControls
就是一个三维坐标轴,通过拖动坐标轴就可以移动与坐标轴绑定的三维模型。
DragControls
方法查看源码可以得知,DragControls
具有选中模型的作用。
构造函数
THREE.DragControls = function ( _objects, _camera, _domElement )
参数
_objects
:对象数组。
_camera
:相机
_domElement
:渲染器
可以添加的事件
dragControls.addEventListener()
hoveron
– 鼠标划中
hoveroff
– 鼠标划过
dragstart
– 开始拖拽
dragend
– 拖拽结束
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>拖拽控件</title>
<style>
body {
margin: 0;
overflow: hidden;
}
#label {
position: absolute;
padding: 10px;
background: rgba(255, 255, 255, 0.6);
line-height: 1;
border-radius: 5px;
}
</style>
<script src="../../libs/build/three-r93.js"></script>
<script src="../../libs/jquery-1.9.1.js"></script>
<script src="../../libs/examples/js/Detector.js"></script>
<script src="../../libs/examples/js/controls/TrackballControls.js"></script>
<script src="../../libs/examples/js/libs/dat.gui.min.js"></script>
<script src="../../libs/examples/js/libs/stats.min.js"></script>
<!--拖拽控件-->
<script src="../../libs/examples/js/controls/DragControls.js"></script>
<!--可视化平移控件-->
<script src="../../libs/examples/js/controls/TransformControls.js"></script>
</head>
<body>
<div id="WebGL-output"></div>
<div id="Stats-output"></div>
<div id="label"></div>
<script>
var stats = initStats();
var scene, camera, renderer, controls, light, selectObject;
// 场景
function initScene() {
scene = new THREE.Scene();
}
// 相机
function initCamera() {
camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 10000);
camera.position.set(0, 400, 600);
camera.lookAt(new THREE.Vector3(0, 0, 0));
}
// 渲染器
function initRenderer() {
if (Detector.webgl) {
renderer = new THREE.WebGLRenderer({antialias: true});
} else {
renderer = new THREE.CanvasRenderer();
}
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x050505);
document.body.appendChild(renderer.domElement);
}
// 初始化模型
function initContent() {
var helper = new THREE.GridHelper(1200, 50, 0xCD3700, 0x4A4A4A);
scene.add(helper);
var cubeGeometry = new THREE.BoxGeometry(100, 100, 100);
var cubeMaterial = new THREE.MeshLambertMaterial({color: 0x9370DB});
var cube = new THREE.Mesh(cubeGeometry, cubeMaterial);
cube.position.y = 50;
cube.name = "cube";
scene.add(cube);
var sphereGeometry = new THREE.SphereGeometry(50, 50, 50, 50);
var sphereMaterial = new THREE.MeshLambertMaterial({color: 0x3CB371});
var sphere = new THREE.Mesh(sphereGeometry, sphereMaterial);
sphere.position.x = 200;
sphere.position.y = 50;
sphere.name = "sphere";
// sphere.position.z = 200;
scene.add(sphere);
var cylinderGeometry = new THREE.CylinderGeometry(50, 50, 100, 100);
var cylinderMaterial = new THREE.MeshLambertMaterial({color: 0xCD7054});
var cylinder = new THREE.Mesh(cylinderGeometry, cylinderMaterial);
cylinder.position.x = -200;
cylinder.position.y = 50;
cylinder.name = "cylinder";
scene.add(cylinder);
}
// 鼠标双击触发的方法
function onMouseDblclick(event) {
// 获取 raycaster 和所有模型相交的数组,其中的元素按照距离排序,越近的越靠前
var intersects = getIntersects(event);
// 获取选中最近的 Mesh 对象
if (intersects.length != 0 && intersects[0].object instanceof THREE.Mesh) {
selectObject = intersects[0].object;
changeMaterial(selectObject);
} else {
alert("未选中 Mesh!");
}
}
// 获取与射线相交的对象数组
function getIntersects(event) {
event.preventDefault();
console.log("event.clientX:" + event.clientX)
console.log("event.clientY:" + event.clientY)
// 声明 raycaster 和 mouse 变量
var raycaster = new THREE.Raycaster();
var mouse = new THREE.Vector2();
// 通过鼠标点击位置,计算出 raycaster 所需点的位置,以屏幕为中心点,范围 -1 到 1
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
//通过鼠标点击的位置(二维坐标)和当前相机的矩阵计算出射线位置
raycaster.setFromCamera(mouse, camera);
// 获取与射线相交的对象数组,其中的元素按照距离排序,越近的越靠前
var intersects = raycaster.intersectObjects(scene.children);
//返回选中的对象
return intersects;
}
// 窗口变动触发的方法
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// 键盘按下触发的方法
function onKeyDown(event) {
switch (event.keyCode) {
case 13:
initCamera();
initControls();
break;
}
}
// 改变对象材质属性
function changeMaterial(object) {
var material = new THREE.MeshLambertMaterial({
color: 0xffffff * Math.random(),
transparent: object.material.transparent ? false : true,
opacity: 0.8
});
object.material = material;
}
// 初始化轨迹球控件
function initControls() {
controls = new THREE.TrackballControls(camera, renderer.domElement);
// controls.noRotate = true;
controls.noPan = true;
// 视角最小距离
controls.minDistance = 1000;
// 视角最远距离
controls.maxDistance = 5000;
}
// 添加拖拽控件
function initDragControls() {
// 添加平移控件
var transformControls = new THREE.TransformControls(camera, renderer.domElement);
scene.add(transformControls);
// 过滤不是 Mesh 的物体,例如辅助网格
var objects = [];
for (let i = 0; i < scene.children.length; i++) {
if (scene.children[i].isMesh) {
objects.push(scene.children[i]);
}
}
// 初始化拖拽控件
var dragControls = new THREE.DragControls(objects, camera, renderer.domElement);
// 鼠标略过事件
dragControls.addEventListener('hoveron', function (event) {
// 让变换控件对象和选中的对象绑定
transformControls.attach(event.object);
});
// 开始拖拽
dragControls.addEventListener('dragstart', function (event) {
controls.enabled = false;
});
// 拖拽结束
dragControls.addEventListener('dragend', function (event) {
controls.enabled = true;
});
}
// 初始化灯光
function initLight() {
light = new THREE.SpotLight(0xffffff);
light.position.set(-300, 600, -400);
light.castShadow = true;
scene.add(light);
scene.add(new THREE.AmbientLight(0x5C5C5C));
}
// 初始化 dat.GUI
function initGui() {
// 保存需要修改相关数据的对象
gui = new function () {
}
// 属性添加到控件
var guiControls = new dat.GUI();
}
// 初始化性能插件
function initStats() {
var stats = new Stats();
stats.domElement.style.position = 'absolute';
stats.domElement.style.left = '0px';
stats.domElement.style.top = '0px';
document.body.appendChild(stats.domElement);
return stats;
}
// 更新div的位置
function renderDiv(object) {
// 获取窗口的一半高度和宽度
let halfWidth = window.innerWidth / 2;
let halfHeight = window.innerHeight / 2;
// 逆转相机求出二维坐标
let vector = object.position.clone().project(camera);
// 修改 div 的位置
$("#label").css({
left: vector.x * halfWidth + halfWidth,
top: -vector.y * halfHeight + halfHeight - object.position.y
});
// 显示模型信息
$("#label").text("name:" + object.name);
}
// 更新控件
function update() {
stats.update();
controls.update();
controls.handleResize();
transformControls.update();
}
// 初始化
function init() {
initScene();
initCamera();
initRenderer();
initContent();
initLight();
initControls();
initGui();
initDragControls();
// addEventListener('dblclick', onMouseDblclick, false);
addEventListener('resize', onWindowResize, false);
addEventListener('keydown', onKeyDown, false);
}
function animate() {
if (selectObject != undefined && selectObject != null) {
renderDiv(selectObject);
}
requestAnimationFrame(animate);
renderer.render(scene, camera);
update();
}
init();
animate();
</script>
</body>
</html>