import * as THREE from "three"; import { AxesHelper, BoxGeometry, Mesh, MeshLambertMaterial, PerspectiveCamera, Vector2, } from "three"; import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer"; import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass"; import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass"; import { FBXLoader } from "three/examples/jsm/loaders/FBXLoader"; export default class ThreeHelper { width: number = 0; height: number = 0; group: THREE.Group; geometry: THREE.BoxGeometry; material: THREE.MeshLambertMaterial; mesh: THREE.Mesh; camera: THREE.PerspectiveCamera; axesHelper: THREE.AxesHelper; scene: THREE.Scene; light: THREE.PointLight; renderer: THREE.WebGLRenderer | null = null; constructor() { this.group = new THREE.Group(); this.geometry = new BoxGeometry(60, 90, 80); this.material = new MeshLambertMaterial({ color: 0x009900, transparent: true, opacity: 0.5, }); this.mesh = new Mesh(this.geometry, this.material); this.group.add(this.mesh); this.camera = this.creatCamera(); this.axesHelper = new AxesHelper(400); this.light = this.creatLight(); this.scene = new THREE.Scene(); this.scene.add(this.group); this.scene.add(this.axesHelper); this.scene.add(this.light); } creatLight = () => { const light = new THREE.PointLight(0xffffff, 1.0); light.intensity = 300.0; //光照强度 light.decay = 0.0; //设置光源不随距离衰减 light.position.set(1000, 1000, 1000); return light; }; creatRenderer = (dom: HTMLElement) => { this.width = dom.clientWidth; this.height = dom.clientHeight; const renderer: THREE.WebGLRenderer = new THREE.WebGLRenderer({ antialias: true, }); renderer.setSize(this.width, this.height); renderer.setClearColor(0x000000, 0.4); this.renderer = renderer; // 改变相机观察目标 this.camera.aspect = this.width / this.height; this.camera.updateProjectionMatrix(); // 注意相机控件OrbitControls会影响lookAt设置,注意手动设置OrbitControls的目标参数 const controls = new OrbitControls(this.camera, this.renderer.domElement); // controls.target = mesh.position; // controls.update(); controls.addEventListener("change", () => { this.renderer!.render(this.scene, this.camera); }); // console.log(this.renderer, dom); dom.appendChild(renderer.domElement); this.renderer.render(this.scene, this.camera); }; loadFBX = (src: string, finish: () => void) => { const loader = new FBXLoader(); loader.load( src, (fbx) => { console.log("loadFBX", fbx); this.group.add(fbx); this.renderer!.render(this.scene, this.camera); finish(); }, (event) => { console.log( "loadFBX" + (event.loaded / event.total) * 100 + "% loaded" ); }, (error) => { console.log("loadFBX", error); finish(); } ); }; addOutline = ( width: number = this.width, height: number = this.height, model?: THREE.Object3D ) => { const composer = new EffectComposer(this.renderer!); const renderPass = new RenderPass(this.scene, this.camera); const outlinePass = new OutlinePass( new Vector2(width, height), this.scene, this.camera ); //模型描边颜色,默认白色 outlinePass.visibleEdgeColor.set(0xffff00); //高亮发光描边厚度 outlinePass.edgeThickness = 4; //高亮描边发光强度 outlinePass.edgeStrength = 6; //模型闪烁频率控制,默认0不闪烁 outlinePass.pulsePeriod = 2; composer.addPass(renderPass); composer.addPass(outlinePass); // composer.render(); const that = this; this.renderer!.domElement.addEventListener("click", function (event) { // .offsetY、.offsetX以canvas画布左上角为坐标原点,单位px const px = event.offsetX; const py = event.offsetY; //屏幕坐标px、py转WebGL标准设备坐标x、y //width、height表示canvas画布宽高度 const x = (px / width) * 2 - 1; const y = -(py / height) * 2 + 1; //创建一个射线投射器`Raycaster` const raycaster = new THREE.Raycaster(); //.setFromCamera()计算射线投射器`Raycaster`的射线属性.ray // 形象点说就是在点击位置创建一条射线,射线穿过的模型代表选中 raycaster.setFromCamera(new THREE.Vector2(x, y), that.camera); //.intersectObjects([mesh1, mesh2, mesh3])对参数中的网格模型对象进行射线交叉计算 // 未选中对象返回空数组[],选中一个对象,数组1个元素,选中两个对象,数组两个元素 const toAddModelArray = []; if (model) { toAddModelArray.push(model); } const intersects = raycaster.intersectObjects([ that.mesh, ...toAddModelArray, ]); console.log("射线器返回的对象", intersects); // intersects.length大于0说明,说明选中了模型 if (intersects.length > 0) { outlinePass.selectedObjects = [intersects[0].object]; composer.render(); } }); }; resizeRender = (dom: HTMLElement) => { this.width = dom.clientWidth; this.height = dom.clientHeight; this.renderer!.setSize(this.width, this.height); this.camera.aspect = this.width / this.height; this.camera.updateProjectionMatrix(); this.renderer!.render(this.scene, this.camera); }; private creatCamera = () => { const camera = new PerspectiveCamera( 75, window.innerWidth / window.innerHeight, 0.1, 3000 ); camera.position.set(1000, 1000, 1000); camera.lookAt(0, 0, 0); return camera; }; }