一. 我们在使用Three想要达成开关门动作时,如何避坑?
图一为我们理想例子
图二图三为非理想例子
二. Three开关门如何点击获取该模型?
1. 我们以双击触发该事件,通过鼠标xy轴在屏幕创建three向量位置;
2. 将创建屏幕坐标转换为Three场景中的坐标;
3. 通过THREE.Raycaster发射光线获取到点击模型;
4. 通过TWEEN动画旋转该模型父级场景(重点为旋转父级场景,自身旋转可就如图二喽)rotationY角度;
document.addEventListener('dblclick', onDocumentMouseDown, false);
// 获取与射线相交的对象数组
const onDocumentMouseDown = (event) => {
event.preventDefault();
//1、先基于我们在屏幕上点击的位置创建一个向量
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0.5
);
//2、然后用unproject函数将点击位置转换成Thres.js场景中的坐标
vector = vector.unproject(Three.camera);
//3、用THREE.Raycaster对象向点击位置发射光线
var raycaster = new THREE.Raycaster(Three.camera.position, vector.sub(Three.camera.position).normalize());
var intersects = raycaster.intersectObjects(Three.scene.children, true);
var currObj = intersects[0].object; //currObj为点击到的第一个对象
if (currObj.parent.rotation.y == 0) {
new TWEEN.Tween(currObj.parent.rotation)
.to(
{
y: 1.5,
},
1500
)
.start();
} else {
new TWEEN.Tween(currObj.parent.rotation)
.to(
{
y: 0,
},
300
)
.start();
}
};
三. 模型核心场景代码
1. 重点在于mesh与menGroup;mesh为门模型,menGroup为mesh父级场景;
- 门宽为40,模型默认在中心轴上那么中心轴的位置在门20的位置;
- 我们建立父级场景menGroup将门mesh包裹;
- 父级menGroup向右平移20,此时menGroup中心轴在门右侧门框上,随着父级平移,mesh作为子级也会平移,此时我们将mesh门本身向左平移-20,门又回到了原始位置,但是父级menGroup的中心轴还停留在右侧门框;
- 此时参考上边currObj.parent.rotation代码,我们获取到门之后旋转父级,大功告成;
- 注意事项:不平移自转为图二,平移计算不对为图三;
// 立方体
const cube = () => {
const cubeSize = 40;
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, 1);
const cubeMat = new THREE.MeshPhongMaterial({ color: '#8AC' });
const mesh = new THREE.Mesh(cubeGeo, cubeMat); // 创景门模型
mesh.position.set(-cubeSize / 2, 0, 0);
const menGroup = new THREE.Group();
menGroup.position.set(cubeSize, 0, 0);
menGroup.add(mesh);
Three.scene.add(menGroup);
};
四. react版全部代码
import React, { useRef, useEffect, useMemo } from 'react';
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
import TWEEN from 'tween/tween.js';
const ThreeRoom = () => {
const createDom = useRef(null);
const ThreeModel = useRef({
scene: null,
camera: null,
renderer: null,
controller: null,
});
const Three = ThreeModel.current;
// 相机加载
const cameraInit = () => {
Three.camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 2000);
Three.camera.position.set(0, 50, 50);
// Three.camera.lookAt(0, 0, 0);
};
// 渲染器实例
const renderer = () => {
Three.renderer = new THREE.WebGL1Renderer();
Three.renderer.setSize(createDom.current.clientWidth, createDom.current.clientHeight);
Three.renderer.setPixelRatio(window.devicePixelRatio);
Three.renderer.setClearAlpha(0);
Three.renderer.setClearColor('rgb(135,206,250)', 1.0);
Three.renderer.setClearColor(0xffffff, 1.0);
Three.renderer.setClearColor('#428bca', 1.0);
Three.renderer.setClearColor('rgba(135,206,250,0.5)', 1.0);
createDom.current.appendChild(Three.renderer.domElement);
};
// 加载场景
const animate = () => {
requestAnimationFrame(animate);
// if (resizeRendererToDisplaySize(Three.renderer)) {
// Three.camera.aspect = window.innerWidth / window.innerHeight;
// Three.camera.updateProjectionMatrix();
// }
TWEEN.update();
Three.renderer.render(Three.scene, Three.camera);
};
// 立方体
const cube = () => {
const cubeSize = 40;
const cubeGeo = new THREE.BoxGeometry(cubeSize, cubeSize, 1);
const cubeMat = new THREE.MeshPhongMaterial({ color: '#8AC' });
const mesh = new THREE.Mesh(cubeGeo, cubeMat); // 创景门模型
mesh.position.set(-cubeSize / 2, 0, 0);
const menGroup = new THREE.Group();
menGroup.position.set(cubeSize, 0, 0);
menGroup.add(mesh);
Three.scene.add(menGroup);
};
// 加载控制器
const controls = () => {
Three.controller = new OrbitControls(Three.camera, Three.renderer.domElement);
Three.controller.maxDistance = 2000;
};
useEffect(() => {
Three.scene = new THREE.Scene();
cameraInit();
cube();
renderer();
controls();
const light = new THREE.AmbientLight(0xffffff, 1);
Three.scene.add(light);
animate();
document.addEventListener('dblclick', onDocumentMouseDown, false);
}, []);
// 获取与射线相交的对象数组
const onDocumentMouseDown = (event) => {
event.preventDefault();
//1、先基于我们在屏幕上点击的位置创建一个向量
var vector = new THREE.Vector3(
(event.clientX / window.innerWidth) * 2 - 1,
-(event.clientY / window.innerHeight) * 2 + 1,
0.5
);
//2、然后用unproject函数将点击位置转换成Thres.js场景中的坐标
vector = vector.unproject(Three.camera);
//3、用THREE.Raycaster对象向点击位置发射光线
var raycaster = new THREE.Raycaster(Three.camera.position, vector.sub(Three.camera.position).normalize());
var intersects = raycaster.intersectObjects(Three.scene.children, true);
var currObj = intersects[0].object; //currObj为点击到的第一个对象
if (currObj.parent.rotation.y == 0) {
new TWEEN.Tween(currObj.parent.rotation)
.to(
{
y: 1.5,
},
1500
)
.start();
} else {
new TWEEN.Tween(currObj.parent.rotation)
.to(
{
y: 0,
},
300
)
.start();
}
};
return <div style={{ width: '100%', height: '100%' }} id="scene" ref={createDom} />;
};
export default ThreeRoom;