|
@@ -0,0 +1,356 @@
|
|
|
+import * as THREE from 'three'
|
|
|
+import {
|
|
|
+ FBXLoader
|
|
|
+} from 'three/examples/jsm/loaders/FBXLoader.js'
|
|
|
+import {
|
|
|
+ OrbitControls
|
|
|
+} from 'three/examples/jsm/controls/OrbitControls.js'
|
|
|
+import {
|
|
|
+ GLTFLoader
|
|
|
+} from 'three/examples/jsm/loaders/GLTFLoader.js';
|
|
|
+import Stats from 'three/examples/jsm/libs/stats.module.js'
|
|
|
+import {
|
|
|
+ TWEEN
|
|
|
+} from 'three/examples/jsm/libs/tween.module.min.js';
|
|
|
+import {
|
|
|
+ FlyControls
|
|
|
+} from 'three/examples/jsm/controls/FlyControls.js'
|
|
|
+import {
|
|
|
+ PointerLockControls
|
|
|
+} from 'three/examples/jsm/controls/PointerLockControls.js'
|
|
|
+
|
|
|
+let camera, scene, renderer, clock, stats, controls, orthCamera, personControl, isMouseDown, pointerControls;
|
|
|
+let moveForward = false;
|
|
|
+let moveBackward = false;
|
|
|
+let moveLeft = false;
|
|
|
+let moveRight = false;
|
|
|
+let canJump = false;
|
|
|
+let prevTime = performance.now();
|
|
|
+const velocity = new THREE.Vector3();
|
|
|
+const direction = new THREE.Vector3();
|
|
|
+const vertex = new THREE.Vector3();
|
|
|
+class threeControls {
|
|
|
+ constructor(option) {
|
|
|
+ this.container = option.container;
|
|
|
+ this.initCamera();
|
|
|
+ this.initSence();
|
|
|
+ this.setDirectionalLight(1, 0, 0);
|
|
|
+ this.setDirectionalLight(0, 1, 0);
|
|
|
+ this.setDirectionalLight(0, 0, 1);
|
|
|
+ this.setDirectionalLight(-1, 0, 0);
|
|
|
+ this.setDirectionalLight(0, -1, 0);
|
|
|
+ this.setDirectionalLight(0, 0, -1);
|
|
|
+ clock = new THREE.Clock();
|
|
|
+ renderer = new THREE.WebGLRenderer({
|
|
|
+ antialias: true
|
|
|
+ });
|
|
|
+ renderer.setPixelRatio(window.devicePixelRatio);
|
|
|
+ renderer.setSize(this.container.offsetWidth, this.container.offsetHeight);
|
|
|
+ renderer.outputEncoding = THREE.sRGBEncoding;
|
|
|
+ renderer.compile(scene, camera);
|
|
|
+ this.container.appendChild(renderer.domElement);
|
|
|
+ }
|
|
|
+ initCamera() { //初始化相机
|
|
|
+ /*
|
|
|
+ * fov — 摄像机视锥体垂直视野角度 默认值是50
|
|
|
+ * aspect — 摄像机视锥体长宽比 默认值是1
|
|
|
+ * near — 摄像机视锥体近端面 默认值是0.1
|
|
|
+ * far — 摄像机视锥体远端面 默认值是2000。
|
|
|
+ */
|
|
|
+ let fov = 45,
|
|
|
+ aspect = this.container.offsetWidth / this.container.offsetHeight,
|
|
|
+ near = 0.1,
|
|
|
+ far = 180;
|
|
|
+ camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
|
|
|
+ camera.position.set(10, 10, 10);
|
|
|
+ }
|
|
|
+ initSence() { //初始化场景
|
|
|
+ scene = new THREE.Scene();
|
|
|
+ //场景背景颜色
|
|
|
+ scene.background = new THREE.Color('rgb(79, 98, 138)');
|
|
|
+ }
|
|
|
+ setHemisphereLight() { //设置半球光;
|
|
|
+ /*
|
|
|
+ * skyColor — (可选参数) 天空中发出光线的颜色 默认值是0xffffff
|
|
|
+ * groundColor — (可选参数) 地面发出光线的颜色 默认值是0xffffff
|
|
|
+ * intensity — 摄像机视锥体近端面 默认值是1
|
|
|
+ */
|
|
|
+ let skyColor = new THREE.Color('rgb(204, 204, 204)'),
|
|
|
+ groundColor = new THREE.Color('rgb(0, 255, 0)'),
|
|
|
+ intensity = 1;
|
|
|
+ let hemiLight = new THREE.HemisphereLight(skyColor, groundColor, intensity);
|
|
|
+ hemiLight.position.set(0, 200, 0);
|
|
|
+ scene.add(hemiLight);
|
|
|
+ }
|
|
|
+ setDirectionalLight(x, y, z) { //设置平行光
|
|
|
+ /*
|
|
|
+ * color - (可选参数) 16进制表示光的颜色。 缺省值为 0xffffff (白色)。
|
|
|
+ * intensity - (可选参数) 光照的强度。缺省值为1。
|
|
|
+ */
|
|
|
+ let color = new THREE.Color('rgb(255,255,255)'),
|
|
|
+ intensity = 1;
|
|
|
+ let dirLight = new THREE.DirectionalLight(color, intensity);
|
|
|
+ //光线从哪照射,例如(0, 1, 0)从上往下
|
|
|
+ dirLight.position.set(x, y, z);
|
|
|
+ //是否会产生阴影
|
|
|
+ dirLight.castShadow = false;
|
|
|
+ scene.add(dirLight);
|
|
|
+ }
|
|
|
+ setGridHelper() { //坐标格辅助对象. 坐标格实际上是2维线数组.
|
|
|
+ /*
|
|
|
+ * size — 坐标格尺寸. 默认为 10
|
|
|
+ * divisions — 坐标格细分次数. 默认为 10
|
|
|
+ * colorCenterLine — 中线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x444444
|
|
|
+ * colorGrid — 坐标格网格线颜色. 值可以为 Color 类型, 16进制 和 CSS 颜色名. 默认为 0x888888
|
|
|
+ */
|
|
|
+ let size = 2000,
|
|
|
+ divisions = 200,
|
|
|
+ colorCenterLine = new THREE.Color('rgb(0,0,255)'),
|
|
|
+ colorGrid = new THREE.Color('rgb(0,0,255)')
|
|
|
+ let grid = new THREE.GridHelper(2000, 200, colorCenterLine, colorGrid);
|
|
|
+ grid.material.opacity = 1;
|
|
|
+ grid.material.transparent = true;
|
|
|
+ scene.add(grid);
|
|
|
+ }
|
|
|
+ setAxisHelper() { //坐标轴
|
|
|
+ //红色代表 X 轴. 绿色代表 Y 轴. 蓝色代表 Z 轴.
|
|
|
+ let axesHelper = new THREE.AxesHelper(200);
|
|
|
+ scene.add(axesHelper);
|
|
|
+ }
|
|
|
+ loaderFBX() { //加载fbx模型
|
|
|
+ const loader = new FBXLoader();
|
|
|
+ loader.load('./model/test.fbx', (object) => {
|
|
|
+ object.scale.multiplyScalar(0.9);
|
|
|
+ object.position.y = 0;
|
|
|
+ object.position.x = 0;
|
|
|
+ scene.add(object);
|
|
|
+ this.renderer.render(scene, camera);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ loaderGLTF(url) { //加载GLTF模型
|
|
|
+ const loader = new GLTFLoader();
|
|
|
+ let _self = this;
|
|
|
+ loader.load(url, gltf => {
|
|
|
+ console.log('加载成功');
|
|
|
+ gltf.scene.scale.set(1, 1, 1)
|
|
|
+ let group = gltf.scene;
|
|
|
+ let box = new THREE.Box3().setFromObject(group);
|
|
|
+ let mdlen = box.max.x - box.min.x; //模型长度
|
|
|
+ let mdwid = box.max.z - box.min.z; //模型宽度
|
|
|
+ let mdhei = box.max.y - box.min.y; //模型高度
|
|
|
+ let x1 = box.min.x + mdlen / 2
|
|
|
+ let y1 = box.min.y + mdhei / 2
|
|
|
+ let z1 = box.min.z + mdwid / 2
|
|
|
+ group.position.set(-x1, -y1, -z1);
|
|
|
+ camera.position.set((mdlen / 2) + 10, (mdlen / 2) + 10, (mdlen / 2) + 100);
|
|
|
+ scene.add(group);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ setrgb() { //随机颜色
|
|
|
+ return `rgb(${Math.floor(Math.random()*256)},${Math.floor(Math.random()*256)},${Math.floor(Math.random()*256)})`
|
|
|
+ }
|
|
|
+ orthCameraLookDownView() { //主视图
|
|
|
+ let perCamera = camera;
|
|
|
+ let target = scene.position.clone();
|
|
|
+ let camPos = perCamera.position.clone();
|
|
|
+ let depth = camPos.sub(target).length();
|
|
|
+ const tmpCamera = this.createPerCameraToOrthCamera(perCamera);
|
|
|
+ tmpCamera.position.set(0, depth, 0);
|
|
|
+ tmpCamera.rotation.set(-Math.PI / 2, 0, 0);
|
|
|
+ camera = tmpCamera;
|
|
|
+ }
|
|
|
+ createPerCameraToOrthCamera(perCamera) {
|
|
|
+
|
|
|
+ if (perCamera == null) {
|
|
|
+ console.error(' perCamera is null');
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (orthCamera == null) {
|
|
|
+ //1.计算透视相机到场景 scene 的深度距离 depth
|
|
|
+ let target = scene.position.clone();;
|
|
|
+ let camPos = perCamera.position.clone();
|
|
|
+ let depth = camPos.sub(target).length();
|
|
|
+
|
|
|
+ //2.得到透视相机的宽高比和垂直可视角度
|
|
|
+ let aspect = perCamera.aspect;
|
|
|
+ let fov = perCamera.fov;
|
|
|
+
|
|
|
+ //3.根据上述变量计算正交投影相机的视口矩形
|
|
|
+ let top_ortho = depth * Math.atan((Math.PI / 180) * (fov) / 2);
|
|
|
+ let right_ortho = top_ortho * aspect;
|
|
|
+ let bottom_ortho = -top_ortho;
|
|
|
+ let left_ortho = -right_ortho;
|
|
|
+
|
|
|
+
|
|
|
+ //4.最后创建正交投影相机
|
|
|
+ let near = perCamera.near;
|
|
|
+ let far = perCamera.far;
|
|
|
+ orthCamera = new THREE.OrthographicCamera(
|
|
|
+ left_ortho, right_ortho,
|
|
|
+ top_ortho, bottom_ortho,
|
|
|
+ near, far
|
|
|
+ );
|
|
|
+ }
|
|
|
+ return orthCamera;
|
|
|
+ }
|
|
|
+ animateCamera(position) {
|
|
|
+ new TWEEN.Tween(position).to({
|
|
|
+ x: position.x + 5,
|
|
|
+ y: position.y + 5,
|
|
|
+ z: position.z + 5
|
|
|
+ }, 1000).easing(TWEEN.Easing.Cubic.InOut).start();
|
|
|
+ }
|
|
|
+ setState() { //fps
|
|
|
+ stats = new Stats();
|
|
|
+ this.container.appendChild(stats.dom);
|
|
|
+ }
|
|
|
+ inserClickFunc() {
|
|
|
+ let _self = this;
|
|
|
+ document.addEventListener('click', (event) => {
|
|
|
+ event.preventDefault();
|
|
|
+ var vector = new THREE.Vector3(
|
|
|
+ (event.clientX / window.innerWidth) * 2 - 1,
|
|
|
+ -(event.clientY / window.innerHeight) * 2 + 1,
|
|
|
+ 0.5
|
|
|
+ )
|
|
|
+ vector = vector.unproject(camera);
|
|
|
+ var raycaster = new THREE.Raycaster(camera.position, vector.sub(camera
|
|
|
+ .position).normalize());
|
|
|
+ var intersects = raycaster.intersectObjects(scene.children, true);
|
|
|
+ if (intersects.length == 0) return;
|
|
|
+ if (intersects.length > 0) {
|
|
|
+ intersects[0].object.material.color.set("#59a4a3");
|
|
|
+ console.log(intersects[0].object);
|
|
|
+ // _self.animateCamera(intersects[0].object.parent.position);
|
|
|
+ // _self.orthCameraLookDownView();
|
|
|
+ }
|
|
|
+ }, false)
|
|
|
+ }
|
|
|
+ OrbitControls() {
|
|
|
+ controls = new OrbitControls(camera, this.container);
|
|
|
+ controls.enableRotate = true; //启用旋转
|
|
|
+ controls.enablePan = true; //启用平移
|
|
|
+ controls.enableZoom = true; //启用缩放
|
|
|
+ // controls.update();
|
|
|
+ }
|
|
|
+ FlyControls() {
|
|
|
+ personControl = new FlyControls(camera, renderer.domElement);
|
|
|
+ personControl.movementSpeed = 14 //移动速度
|
|
|
+ personControl.rollSpeed = Math.PI / 1 //翻滚角速度
|
|
|
+ personControl.autoForward = false //自动向前关闭
|
|
|
+ personControl.dragToLook = true //拖动视图变换关闭
|
|
|
+ document.addEventListener('mousedown', () => {
|
|
|
+ isMouseDown = true;
|
|
|
+ })
|
|
|
+ document.addEventListener('mouseup', () => {
|
|
|
+ isMouseDown = false;
|
|
|
+ })
|
|
|
+ }
|
|
|
+ initPointerLockControls() {
|
|
|
+ pointerControls = new PointerLockControls(camera, document.body);
|
|
|
+ const blocker = document.getElementById('blocker');
|
|
|
+ const instructions = document.getElementById('instructions');
|
|
|
+
|
|
|
+ instructions.addEventListener('click', function() {
|
|
|
+ pointerControls.lock();
|
|
|
+ this.style.display = 'none'
|
|
|
+ });
|
|
|
+ pointerControls.addEventListener('lock', function() {
|
|
|
+ instructions.style.display = 'none';
|
|
|
+ blocker.style.display = 'none';
|
|
|
+
|
|
|
+ });
|
|
|
+ pointerControls.addEventListener('unlock', function() {
|
|
|
+ blocker.style.display = 'block';
|
|
|
+ instructions.style.display = '';
|
|
|
+ });
|
|
|
+
|
|
|
+ const onKeyDown = function(event) {
|
|
|
+ switch (event.code) {
|
|
|
+ case 'ArrowUp':
|
|
|
+ case 'KeyW':
|
|
|
+ moveForward = true;
|
|
|
+ break;
|
|
|
+ case 'ArrowLeft':
|
|
|
+ case 'KeyA':
|
|
|
+ moveLeft = true;
|
|
|
+ break;
|
|
|
+ case 'ArrowDown':
|
|
|
+ case 'KeyS':
|
|
|
+ moveBackward = true;
|
|
|
+ break;
|
|
|
+ case 'ArrowRight':
|
|
|
+ case 'KeyD':
|
|
|
+ moveRight = true;
|
|
|
+ break;
|
|
|
+ case 'Space':
|
|
|
+ if (canJump === true) velocity.y += 200;
|
|
|
+ canJump = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ const onKeyUp = function(event) {
|
|
|
+ switch (event.code) {
|
|
|
+ case 'ArrowUp':
|
|
|
+ case 'KeyW':
|
|
|
+ moveForward = false;
|
|
|
+ break;
|
|
|
+ case 'ArrowLeft':
|
|
|
+ case 'KeyA':
|
|
|
+ moveLeft = false;
|
|
|
+ break;
|
|
|
+ case 'ArrowDown':
|
|
|
+ case 'KeyS':
|
|
|
+ moveBackward = false;
|
|
|
+ break;
|
|
|
+ case 'ArrowRight':
|
|
|
+ case 'KeyD':
|
|
|
+ moveRight = false;
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ };
|
|
|
+ document.addEventListener('keydown', onKeyDown);
|
|
|
+ document.addEventListener('keyup', onKeyUp);
|
|
|
+ }
|
|
|
+ render() { //渲染
|
|
|
+ animate()
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+function animate() {
|
|
|
+
|
|
|
+ if (personControl && isMouseDown) personControl.update(clock.getDelta());
|
|
|
+ if (stats) stats.update();
|
|
|
+ const time = performance.now();
|
|
|
+
|
|
|
+ if (pointerControls && pointerControls.isLocked === true) {
|
|
|
+
|
|
|
+ const delta = (time - prevTime) / 1000;
|
|
|
+ velocity.x -= velocity.x * 5.0 * delta;
|
|
|
+ velocity.z -= velocity.z * 5.0 * delta;
|
|
|
+ velocity.y -= 9.8 * 50.0 * delta; // 100.0 = mass
|
|
|
+
|
|
|
+ direction.z = Number(moveForward) - Number(moveBackward);
|
|
|
+ direction.x = Number(moveRight) - Number(moveLeft);
|
|
|
+ direction.normalize(); // this ensures consistent movements in all directions
|
|
|
+
|
|
|
+ if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta;
|
|
|
+ if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta;
|
|
|
+
|
|
|
+ pointerControls.moveRight(-velocity.x * delta);
|
|
|
+ pointerControls.moveForward(-velocity.z * delta);
|
|
|
+ pointerControls.getObject().position.y += (velocity.y * delta); // new behavior
|
|
|
+
|
|
|
+ if (pointerControls.getObject().position.y < 10) {
|
|
|
+ velocity.y = 0;
|
|
|
+ pointerControls.getObject().position.y = 10;
|
|
|
+ canJump = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ prevTime = time;
|
|
|
+ renderer.render(scene, camera);
|
|
|
+ requestAnimationFrame(animate);
|
|
|
+}
|
|
|
+export default threeControls;
|