前言
本次博客主要是使用射线实现鼠标标记几何体进行交互(碰触、点击),还实现了一个让加载的物体实现沿设定的轨道进行移动。
正文
这次算是把上一次的坑给填上了。简单说一下绕Y轴旋转的点光源吧(就是上图中那个白色的球形几何体)我的思路是创建一个球形几何体加一个点光源,设置他们在同一个位置上。至于旋转的话需要用到三维变换矩阵的知识,例如绕着Y轴旋转
那么关于他们的位置需要这样设置
//这两行是拿到系统开始的时间
let clock = new THREE.Clock()
const elapsed = clock.getElapsedTime()
//这是俯视的情况下的顺时针旋转,逆时针改变cos和sin的顺序即可
sphere.position.set(Math.cos(elapsed) * 50, 30, Math.sin(elapsed) * 50);
pointLight.position.set(Math.cos(elapsed) * 50, 30, Math.sin(elapsed) * 50);
关于GUI的使用:
const lightColor = {
color: '#ffffff'
}gui.addFolder("点光源").addColor(lightColor, 'color').onChange(() => {
pointLight.color.set(lightColor.color);
sphere.material.color.set(lightColor.color);
});
好了,到了重头戏了!
首先是需要生成随机位置的立方体:
let group = new THREE.Group()
const cubeGeometry = new THREE.BoxGeometry(20, 20, 20)
for (let i = 0; i < 20; i++) {
const object = new THREE.Mesh(cubeGeometry, new THREE.MeshLambertMaterial({
color: Math.random() * 0xffffff
}));
object.position.x = Math.random() * 800 - 400;
object.position.y = Math.random() * 800 - 400;
object.position.z = Math.random() * 800 - 400;group.add(object)
}scene.add(group)
这里面涉及group(组)的概念,我的理解就是在放置场景之前可以将一些需要特殊处理的几何体放进group里和其他场景物体区分开,本例中就是只会对组里生成的立方体进行交互。这样的话结构更清晰一些。
接下来就是光线投射进行鼠标拾取
const raycaster = new THREE.Raycaster()
const mouse = new THREE.Vector2()let selectedObject = null;
//核心函数
function onDocumentMouseMove(event) {
if (selectedObject) {selectedObject.material.color.set('#69f');
selectedObject = null;}
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;// 通过射线投射判断是否有几何体被接触,标准化二维坐标
raycaster.setFromCamera(mouse, camera);//遍历接触的几何体
const intersects = raycaster.intersectObject(group, true)
if (intersects.length > 0) {const res = intersects.filter(function(res) {
return res && res.object;
})[0];
if (res && res.object) {
selectedObject = res.object;
selectedObject.material.color.set('#f00');}
}
}
window.addEventListener('mousemove', onDocumentMouseMove)
当鼠标移动时raycaster时实时改变的,当移动到目标物体时会得到他的res指的是整个的所有属性(uv,normal)等,而object对象,里面包含的position等属性。触碰时变红,离开变为蓝色
点击变色的话和他区别不大,就是监听改为click即可
最后说一下轨迹移动控制,用到的模块
import {
TransformControls
} from 'three/addons/controls/TransformControls.js';
import {
FontLoader
} from 'three/addons/loaders/FontLoader.js';
import {
TextGeometry
} from 'three/addons/geometries/TextGeometry.js';
import {
Flow
} from 'three/addons/modifiers/CurveModifier.js';
TransformControls该类可提供一种类似于在数字内容创建工具(例如Blender)中对模型进行交互的方式,来在3D空间中变换物体。 和其他控制器不同的是,变换控制器不倾向于对场景摄像机的变换进行改变。
下面是生成轨道以及字体
const textLoader = new FontLoader(); textLoader.load("../node_modules/three/examples/fonts/helvetiker_regular.typeface.json", function(font) {
const geometry = new TextGeometry('Vue+three.js!', {
font: font,
size: 20,
height: 1,
curveSegments: 12,
bevelEnabled: true,
bevelThickness: 0.02,
bevelSize: 0.01,
bevelOffset: 0,
bevelSegments: 5,
});geometry.rotateX(Math.PI);
const material = new THREE.MeshStandardMaterial({
color: 0x99fff0
});const objectToCurve = new THREE.Mesh(geometry, material);
flow = new Flow(objectToCurve);
flow.updateCurve(0, curve);
scene.add(flow.object3D);
})//用于控制轨道的4个立方体
const initialPoints = [{
x: 80,
y: 0,
z: -80
},
{
x: 80,
y: 0,
z: 80
},
{
x: -80,
y: 0,
z: 80
},
{
x: -80,
y: 0,
z: -80
},
];const boxGeometry = new THREE.BoxGeometry(10, 10, 10);
const boxMaterial = new THREE.MeshBasicMaterial();for (const handlePos of initialPoints) {
const handle = new THREE.Mesh(boxGeometry, boxMaterial);
handle.position.copy(handlePos);
curveHandles.push(handle);
scene.add(handle);}
//生成轨道路径
curve = new THREE..CatmullRomCurve3(
curveHandles.map((handle) => handle.position)
);curve.curveType = 'centripetal';
curve.closed = true;const points = curve.getPoints(50);
line = new THREE.LineLoop(
new THREE.BufferGeometry().setFromPoints(points),
new THREE.LineBasicMaterial({
color: 0x0fff00
})
);
scene.add(line);//当改变路径时重新计算坐标
control2 = new TransformControls(camera, renderer.domElement);
control2.addEventListener('dragging-changed', function(event) {if (!event.value) {
const points = curve.getPoints(50);
line.geometry.setFromPoints(points);
flow.updateCurve(0, curve);}
});
理解一下就是路径是通过给定的坐标,然后用.CatmullRomCurve3拿到三维曲线,接着从曲线上取一定数量的点来生成线可视化出来。
if (action === ACTION_SELECT) {
raycaster.setFromCamera(mouse, camera);
action = ACTION_NONE;
const intersects = raycaster.intersectObjects(curveHandles, false);
if (intersects.length) {
const target = intersects[0].object;
control2.attach(target);
scene.add(control2);
}
}
if (flow) {
flow.moveAlongCurve(0.001);
}
render();
鼠标点击立方体之后,射线得到鼠标坐标,这时拿到对应的对象,对其进行处理
这里我遇到了一个问题就是场景中有轨道控制器和这个单独对模型的控制器。会出现冲突,也就是在移动模型时也会导致摄像机改变。解决这个问题我通过声明一个布尔值来改变control中的enabled(false为不影响用户操作)
//记录鼠标交互
let isInteracting = false
function isInteractingtest(){
if(!isInteracting){
isInteracting =true
control.enabled = false
} else {
isInteracting =false
control.enabled =true
}
console.log(isInteracting)
}
总结:
最后想说一点真心话, 算是完成了之前的想法,后续的话想学习一下shader实现个简单的效果就好了,因为之前有过webgl的学习,学起来应该会好一些。其实我之前是想找个暑假实习的,奈何岗位太少了面试了3家,拿了一家offer但不太想去。通过面试也是让我重新认识到自己(一定要熟悉自己项目),哎,真难啊现在这局势,所以现在就努力秋招吧,看看这两个项目能不能帮我拿到一个好点的offer。