import { OrbitControls } from '../../node_modules/three/examples/jsm/controls/OrbitControls';//控制器
import { GLTFLoader } from '../../node_modules/three/examples/jsm/loaders/GLTFLoader';//glb模型加载器
import { Mesh, PerspectiveCamera, Scene, WebGLRenderer, PointLight, AmbientLight, AxesHelper, DoubleSide, PlaneGeometry, MeshPhongMaterial, Clock, AnimationMixer, GridHelper, Raycaster, Vector2, Object3D } from 'three';
export class LightProbeDemo {
private camera: PerspectiveCamera;
private scene: Scene;
private renderer: WebGLRenderer;
private gltfLoader:GLTFLoader;
private animationMixer:AnimationMixer;
private clock: Clock;
private action!:AnimationAction;
constructor() {
// 创建场景
this.scene = new Scene();
this.animationMixer = new AnimationMixer(this.scene); //初始化帧动画混合器
this.gltfLoader = new GLTFLoader();
this.clock = new Clock();//初始化时钟对象
// 创建渲染器
this.renderer = new WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setPixelRatio(window.devicePixelRatio); //设备像素比 可以清晰物体
this.renderer.setClearColor(0xb9d3ff, 1); //设置背景颜色
document.body.appendChild(this.renderer.domElement);
// 创建相机
this.camera = new PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
this.camera.position.set(0, 5, 10);
this.camera.lookAt(this.scene.position);
// 平面
const planeGeo = new PlaneGeometry(100, 100);
const plane = new Mesh(planeGeo, new MeshPhongMaterial({ color:0xffffff, side:DoubleSide }));
plane.name='plane';
plane.rotation.x = -Math.PI/2;
this.scene.add(plane);
this.scene.add(new GridHelper(100, 100));
// glb模型加载
this.gltfLoader.load('../assets/Soldier.glb', gltf=>{
console.log(gltf);
gltf.scene.name = 'Soldier'; //标识模型
this.scene.add(gltf.scene);
// 获取剪辑clip对象
const animationClip = gltf.animations.find(animationClip=>animationClip.name==='Walk');
if(animationClip){
// 通过混合器的剪辑动作返回一个帧动画的操作对象
this.action = this.animationMixer.clipAction(animationClip);
this.action.play();//帧动画播放
}
});
// 处理点击事件
this.renderer.domElement.addEventListener('click', e=>{
// 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)
const x = ( e.clientX / window.innerWidth ) * 2 - 1;
const y = - ( e.clientY / window.innerHeight ) * 2 + 1;
const mousePoint = new Vector2(x, y); //拿到鼠标点击的坐标
const raycaster=new Raycaster();
// 通过摄像机和鼠标位置更新射线
raycaster.setFromCamera( mousePoint, this.camera );
const intersects = raycaster.intersectObjects(this.scene.children, true); //第二个参数为true表示递归遍历
// 判断是否点击在模型上
const intersect = intersects.filter(intersect=>!(intersect.object instanceof GridHelper) && intersect.object.name!=='plane')[0];
// 确保只点击在Soldier模型上面
if(intersect && this.isClickSoldier(intersect.object)){
this.action.stop();
}
});
// 点光源
const pointLight = new PointLight(0xffffff, 2);
pointLight.position.set(100, 200, 100);
this.scene.add(pointLight);
// 环境光
const envlight = new AmbientLight(0xffffff, 2);
this.scene.add(envlight);
// 辅助坐标系
var axis = new AxesHelper(250);
// this.scene.add(axis);
new OrbitControls(this.camera, this.renderer.domElement);
window.addEventListener('resize', () => this.onWindowResize());
this.render();
}
private isClickSoldier(object:Object3D) {
if(object.name=='Soldier'){
return object;
}else if(object.parent){
return this.isClickSoldier(object.parent);
}else{
return null;
}
}
private onWindowResize() {
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix(); //相机属性发生变化更新投影矩阵
}
private render() {
this.animationMixer.update(this.clock.getDelta()); //获取两帧之间的时间 然后更新帧动画(这个不写的话不能进行帧动画播放)
window.requestAnimationFrame(() => this.render());
this.renderer.render(this.scene, this.camera);
}
}
three.js中的射线拾取以及坐标转换原理可以参考以下文章:
https://zhuanlan.zhihu.com/p/143642146